如何在使用服务定位器模式时使用模拟进行测试



前言:我的默认操作模式是使用IoC容器和构造函数注入。

我开始开发一个IntelliJ插件,我想利用控制反转。因为这是一个插件,没有容器的选项(对吧?),所以我想我需要使用服务定位器模式。

如何使用服务定位器模式的mock进行测试?

我能想到的最好的方法是为我的定位器使用一个接口,使用静态getter在每个服务的默认构造函数中设置它,并有一个setter,这样我就可以设置一个模拟的定位器。它看起来像这样:

public class MyService {
    private IServiceLocator locator;
    public MyService() {
        setLocator(ServiceLocator.locator());
    }
    public void setLocator(IServiceLocator locator) {
        this.locator = locator;
    }
}

现在我可以在测试中模拟IServiceLocator并将其设置在MyService上。然后我可以期待一个类似locator.dependency1()的调用,并使它返回一个模拟的依赖项。

我对这种方法的主要问题是定位器设置器只用于支持测试。有没有更好的方法?

首先,我建议阅读优秀的"有效地使用遗留代码",其中有很多处理此类事情的模式。尽管你在写新代码,你还是会受到一些与遗留代码相同的限制。

对于这种情况,一个更简单的方法可能是提供第二个受保护的构造函数,它显式地接受你的依赖项,并让无参数构造函数使用你想要的默认值。所以像这样:
 public class MyService {
    private Dependency1 dep1;
    private Dependency2 dep2;
    protected MyService(Dependency1 dep1, Dependency2 dep2) {
        this.dep1 = dep1;
        this.dep2 = dep2;
    }
    public MyService() {
         this(new ConcreteDependency1(), new ConcreteDependency2());
    }
}

你也可以添加一个注释来明确这是用于测试的,例如Guava的@VisibleForTesting

你可以用你描述的方式来做。我想到了其他的选择:

你可以使用包私有访问服务定位器字段

现在,如果你把你的测试类和服务放在同一个包中——它可以直接操作被测试类中的字段——所以很容易模拟服务定位器。

你可以使用测试/模拟库的特性来注入服务定位器

例如Mockito (https://code.google.com/p/mockito/)可以通过两种方式注入私有字段:

  • Whitebox类-但这不是最好的主意,因为您必须知道您想要模拟的字段名称(更改字段名称将中断测试),并且您需要手动执行Withebox.setInternalState方法
  • @InjectMocks注释-您创建了@Mock注释的服务定位字段,@InjectMocks和mockito注释的服务字段将使用服务定位字段并自动注入到服务中。

你可以使用不依赖于容器的依赖注入机制

例如,检查Google Guice库:https://code.google.com/p/google-guice/

你可以使用"手动"依赖注入

DI是模式- DI容器/框架使这种模式易于实现,但你总是可以自己用工厂类来实现(但这需要更多的时间)。这里有一个关于DI的很好的讨论,包括"手动"DI: https://www.youtube.com/watch?v=RlfLCWKxHJ0

很难说哪个选项最适合你。如果你想坚持使用服务定位器-我推荐Mockito。如果你想要DI -我推荐Google Guice。我个人认为,服务定位器更像是反模式。

最新更新