深度模拟不是@InjectMocks

时间:2018-06-04 10:16:46

标签: java unit-testing mockito

注释@InjectMocks为我们提供了存根/填充私有成员并重用测试用例的方法。这是概念代码,当我们对假冒成员进行填充时,问题就出现了。

  public class TestBuilder{
      @Spy
      private StubComponent componentA = new StubComponent();
      @Mock
      private FakeComponent componentB;
      @InjectMocks
      private class TestTarget targetInstance = mock(TestTarget.class);

      public static Class TestTarget{
        private StubComponent componentA;
        private FakeComponent componentB;
        public ShimmedResultB testInvokation(String para){
            componentA.doCallRealMethod();
            ShimmedResultA shimmedResultA = componentA.someUnableToStubbedMethod(para);
            ShimmedResultB shouldNotBeNull = componentB.someShimmedMethod(shimmedResultA);
            return shouldNotBeNull;
        }
      }

      private TestBuilder(){
        MockitoAnnotations.initMocks(this);
        //Shim the real component A with partial stubbed
        doReturn(shimmedResultA).when(componentA).someUnableToStubbedMethod(any());
        //Shim the fake component B
        //************The issue is here****************
        componentB = mock(FakeComponent.class);
        //*********************************************
        when(componentB.someShimmedMethod(any())).thenReturn(shimmedResultB);

      }
      public TestTarget getTargetInstance(){
        return this.targetInstance;
      }

      public static TestTarget build(){
        return (new TestBuilder()).getTargetInstance();
      }

      public static main(String[] args){
        TestTarget testInstance = TestBuilder.build();
        ShimmedResultB result = testInstance.testInvokation("");
        assertThat(result, not(equalTo(null)));
      }
  }

问题在于我们嘲笑假componentB。然后someShimmedMethod将返回null。似乎InjectMocks无法将mock()传递给私人会员。

以下是一些术语定义:

  1. StubComponent:测试将作为私有成员渗透到此组件。但是,有一些方法可能无法通过。我们可以采用其公共方法。该组件可能具有较小的依赖性范围,很容易由本地资源启动。

  2. FakeComponent:此组件将在其他地方进行测试。在这里,我们只能构建模拟实例并填充测试目标将利用的所有方法。

  3. 存根:@Spy可以帮助我们勾住Stubbed成员。私人会员并非100%真实。但是一些短暂的部分可以让测试渗透到这个私人成员中。

  4. Shim:@Mock会在initMocks之前给我们一个空指针。所以我们可以在initMocks之后开始设计Fake组件的返回。这是@InjectMocks的神奇之处。然而,这是最棘手的部分,因为Developer想要直观地启动componentB的每一件事并模拟(FakeComponent.class)。这将清除所有的垫片设计并使你的断言失败。

  5. =============================================== ===================

    感谢Maciej的回答,并在翻译我的测试用例结构时抱歉。让我向Maciej的答案提出更清晰的描述。

      public class TestBuilder{
          @Spy
          private StubComponent componentA = new StubComponent();
          @Mock
          private FakeComponent componentB;
          @InjectMocks
          private TestTarget targetInstance = mock(TestTarget.class);
    
          public static Class TestTarget{
            private StubComponent componentA;
            private FakeComponent componentB;
            public ShimmedResultB testInvokation(String para){
                componentA.doCallRealMethod();
                ShimmedResultA shimmedResultA = componentA.someUnableToStubbedMethod(para);
                ShimmedResultB shouldNotBeNull = componentB.someShimmedMethod(shimmedResultA);
                return shouldNotBeNull;
            }
    
            public TestTarget(){
                //The FakeComponent has some specific remote resource
                //And could not be initialized here
                componentB = new FakeComponent();
                //We will use mock server to test this FakeComponent else where
            }
          }
    
          private TestBuilder(){
            //Hook the testing Function for trigger the step in
            doCallRealMethod().when(this.targetInstance).testInvokation(anyString());
            //Inject Stubbed and Faked Private Member for testing
            MockitoAnnotations.initMocks(this);
            //Shim the real component A with partial stubbed
            doReturn(shimmedResultA).when(componentA).someUnableToStubbedMethod(any());
    
            //************The issue is here****************
            componentB = mock(FakeComponent.class);
            //*********************************************
            //Shim the leveraged method of fake componentB
            when(componentB.someShimmedMethod(any())).thenReturn(shimmedResultB);
          }
    
          public TestTarget getTargetInstance(){
            return this.targetInstance;
          }
    
          public static TestTarget build(){
            return (new TestBuilder()).getTargetInstance();
          }
    
          public static main(String[] args){
            TestTarget testInstance = TestBuilder.build();
            //The doRealCall hook will trigger the testing
            ShimmedResultB result = testInstance.testInvokation("");
            assertThat(result, not(equalTo(null)));
          }
      }
    

    第二个概念代码中添加了一些内容:

    1. componentB是我们不想介入的范围。但是,TestTarget在其构造函数中启动了componentB。当我们有一个与远程源相关的实用程序时,这很常见。我们使用模拟服务器或其他技术独立测试componentB。因此,我们只能使用mock(TestTarget.class)。

    2. 因为我们嘲笑了TestTarget。有一件事我错过了我们需要使用doCallRealMethod()。when(targetInstance)来触发testInvokation()。这限制了targetInstance的null声明。我们需要mock()并挂钩doCallRealMethod。

    3. 因此,结果是我们需要将@Mock保留为null而不使用任何mock()让@InjectMocks来处理填充程序。 我们发现使用@InjectMocks时这很棘手。

1 个答案:

答案 0 :(得分:0)

问题在于@InjectMocks定义:

@InjectMocks
private class TestTarget targetInstance = mock(TestTarget.class);

测试中的类永远不应该是mock(也就是class关键字的原因)。

尝试使用:

@InjectMocks
private TestTarget targetInstance = new TestTarget();

或简单地说:

@InjectMocks
private TestTarget targetInstance;