使用服务定位器模式时如何使用模拟测试?

时间:2014-05-04 19:33:54

标签: java unit-testing intellij-idea intellij-plugin service-locator

前言:我的默认操作模式是使用IoC容器和构造函数注入。这使得使用模拟依赖项进行测试变得微不足道。

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

如何使用服务定位器模式测试模拟?

我能想到的最好的方法是为我的定位器使用一个接口,使用静态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()之类的调用,并使其返回一个模拟的依赖项。

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

2 个答案:

答案 0 :(得分:1)

首先,我建议阅读优秀的"有效地使用遗留代码"它有一堆模式来处理这样的东西。虽然您正在编写新代码,但您仍然受到与遗留代码相同的一些限制。

为此,一种更简单的方法可能是提供第二个受保护的构造函数,该构造函数显式接受您的依赖项,并使no-arg构造函数使用您想要的默认值。如下所示:

 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's @VisibleForTesting

答案 1 :(得分:1)

你可以按照你描述的方式做到这一点。我想到了其他选择:

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

现在,如果您将测试类放在与服务相同的包中 - 它可以直接操作测试类中的字段 - 因此很容易模拟服务定位器。

您可以使用测试/模拟库的功能来注入服务定位器

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

  • Whitebox类 - 但这不是最好的主意,因为您必须知道要模拟的字段名称(更改字段名称将中断测试),并且您需要执行Withebox.setInternalState方法manualy
  • @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。 Personaly,我认为服务定位器更像是反模式。