【问题标题】:How to inject multiple mocks of the same interface如何注入同一接口的多个模拟
【发布时间】:2014-01-30 00:01:39
【问题描述】:

我想测试的Java类(称为ServiceCaller)有这个:

@Autowired @Qualifier(value="serviceA")
SomeService serviceA;

@Autowired @Qualifier(value="serviceB")
SomeService serviceB;

(有一个 doWork() 方法将检查条件并调用 A 或 B)。

如何将每个服务的模拟注入到适当的变量中?

我的Junit 有这个:

@InjectMocks ServiceCaller classUnderTest = new ServiceCaller();

@Mock SomeService mockServiceA;
@Mock SomeService mockServiceB;

然而,当我运行测试以检查在正确条件下调用的服务 A/B 时,我得到空指针,因为尚未注入模拟。

显然是因为对同一接口 (SomeService) 的多个依赖项。有没有办法在声明模拟服务时指定限定符?还是我需要为依赖项设置设置器并设置老式方式?

【问题讨论】:

    标签: java spring dependency-injection mocking mockito


    【解决方案1】:

    将模拟命名为 serviceA 和 serviceB 就足够了。来自 Mockito documentation

    属性设置器注入;模拟将首先按类型解析,然后, 如果有多个相同类型的属性,则通过匹配 属性名称和模拟名称。

    在你的例子中:

    @InjectMocks ServiceCaller classUnderTest;
    
    @Mock SomeService serviceA;
    @Mock SomeService serviceB;
    

    请注意,使用@InjectMocks 时无需手动创建类实例。

    尽管如此,我个人更喜欢使用构造函数注入依赖项。它使在测试中注入模拟变得更容易(只需使用模拟调用构造函数 - 没有反射工具或@InjectMocks(这很有用,但隐藏了某些方面))。此外使用TDD 可以清楚地看到测试类需要哪些依赖项,并且IDE 可以生成您的构造函数存根。

    Spring Framework 完全支持构造函数注入:

    @Bean
    public class ServiceCaller {
        private final SomeService serviceA;
        private final SomeService serviceB;
    
        @Autowired
        public ServiceCaller(@Qualifier("serviceA") SomeService serviceA,
                             @Qualifier("serviceB") SomeService serviceB) { ... }
    
        ...
    }
    

    可以使用以下代码测试此代码:

    @Mock SomeService serviceA;
    @Mock SomeService serviceB;
    
    //in a setup or test method
    ServiceCaller classUnderTest = new ServiceCaller(serviceA, serviceB); 
    

    【讨论】:

    • 太棒了!希望它会像那样简单并且有效。谢谢斯帕克!!!
    • 在最后一行代码中,您可以编写:@InjectMocks ServiceCaller classUnderTest;
    • 是的,但是请阅读我答案中间的文字,以了解为什么我通常更喜欢在测试中显式调用构造函数。
    • 很好的正确答案!我希望通过命名@Mock(name="nameMatchingQualifier"),它们会自动匹配构造函数中的@Qualifier,因此使用@InjectMocks ,而不是从测试中手动调用构造函数。
    • @ACV,@Qualifier 是 Spring 特定的注解,所以它必须使用反射来实现。 Mockito 中的InjectMocks 已经相当复杂(对于新手来说偶尔会感到惊讶 - 例如,假设添加了新的构造函数参数并切换到字段注入,它可以跳过构造函数注入,而不会设置新字段 - null)。因此,您可以在 Mockito 中为此创建票证,但团队可能不愿意实施。
    【解决方案2】:

    您可以使用“名称”属性来定义您的实例,如下所示:

    @Mock(name="serviceA") SomeService serviceA;
    @Mock(name="serviceB") SomeService serviceB;
    

    【讨论】:

    • 您能否提供更多详细信息来解释您的答案如何增加已接受答案尚未提供的任何价值?
    • 如果你在一个字段上自动装配,而不是构造函数(虽然这是不好的做法),这是唯一的方法
    【解决方案3】:

    当您具有相同类型的依赖项时,mockito 会由于相同类型的属性而停止注入依赖项。通过以下方式参考@osiris256 解决此问题:

    class ServiceLayer{
    
       @Autowired
       @Qualifier("bean1")
       private InterfaceA typeA;  
    
       @Autowired
       @Qualifier("bean2")
       private InterfaceA typeB;  
    
    }
    

    你的测试类应该是:

    @RunWith(SpringRunner.class)    
    class ServiceLayerTest{
    
      @Mock(name = "typeA")
      private InterfaceA typeA;  
    
      @Mock(name = "typeB")
      private InterfaceA typeB;  
    
      @InjectMocks
      ServiceLayer serviceLayer;
    
      @Before
      public void initialiseBeforeTest(){
         MockitoAnnotations.initMocks(this);
      }
    
      // here goes your test 
      @Test
       public void test(){
         // use your when then .....
      }
    }
    

    注意:如果您使用 SpringRunner 并使用 @MockBean 这将不起作用, 您必须参考@osiris256 替换为@Mock(name="")。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-15
      • 1970-01-01
      • 2014-09-28
      • 1970-01-01
      • 1970-01-01
      • 2020-12-25
      • 2020-04-09
      相关资源
      最近更新 更多