简体   繁体   English

深层模拟无法作为@InjectMocks的成员运行

[英]The deep mock is not working as member of @InjectMocks

The annotation @InjectMocks brings us the way to stub/shim private member and reuse the testing case. 注释@InjectMocks为我们提供了对存根/填充私有成员@InjectMocks的方法。 Here is the concept code where issue happened when we shim the Fake Member. 这是当我们对假成员进行伪造时发生问题的概念代码。

  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)));
      }
  }

The issue is when we mock the Fake componentB . 问题是当我们模拟Fake componentB Then the someShimmedMethod will return null. 然后someShimmedMethod将返回null。 It seems the InjectMocks could not carry the mock() to private member. 看来InjectMocks无法将mock()携带给私有成员。

Here are some terminology definition: 以下是一些术语定义:

  1. StubComponent: The test would penetrate to this component as private member. StubComponent:测试将作为私有成员渗透到此组件。 However, there is some method might not be able to go through. 但是,有些方法可能无法通过。 We could shim its public method. 我们可以使用其公开方法。 This component may have smaller scope of dependencies which are easily to be initiated by local resource. 该组件可能具有较小的依赖性范围,这些依赖性范围很容易由本地资源启动。

  2. FakeComponent: This component would be testing in somewhere else. FakeComponent:该组件将在其他地方进行测试。 Here we can only build the mocked instance and shim all the methods would be leveraged by test target. 在这里,我们只能构建模拟实例,并填充测试目标将利用的所有方法。

  3. Stub: The @Spy could help us to hook the Stubbed member. 存根:@Spy可以帮助我们钩住存根成员。 The private member is not 100% real. 私人成员不是100%真实的。 But some stubbed part could let test penetrate into this private member. 但是某些残存的部分可能会让测试渗透到该私有成员中。

  4. Shim: The @Mock would give us a null pointer until initMocks. Shim:@Mock将为我们提供一个空指针,直到initMocks。 So we can start to design the return of Fake component after initMocks. 因此,我们可以开始设计initMocks之后的Fake组件的返回。 This is magic of @InjectMocks. 这是@InjectMocks的魔力。 However, this is the most tricky part since Developer would like to initiate every thing and mock(FakeComponent.class) for componentB intuitively. 但是,这是最棘手的部分,因为开发人员希望直观地初始化componentB的所有内容并嘲笑(FakeComponent.class)。 That will clean all the shimmed design and make your assertion failed. 这将清除所有填充的设计,并使您的声明失败。

================================================================== ================================================== ================

Thanks for Maciej's Answer and sorry about the typo when I translate the structure of my test case. 感谢Maciej的答复,对我翻译测试用例的结构时出现的错字表示抱歉。 And let me raise the issue with more clear description to Maciej's Answer. 并且让我对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)));
      }
  }

There are something added in second concept code: 第二个概念代码中添加了一些内容:

  1. The componentB is a scope we don't want to step in. However, the TestTarget has initiation with componentB in its constructor. componentB是我们不希望介入的范围。但是,TestTarget在其构造函数中使用componentB进行初始化。 This is pretty common when we have a utility related to remote source. 当我们有一个与远程源相关的工具时,这很常见。 We use to test componentB independently with mock server or other technique. 我们使用模拟服务器或其他技术来独立测试componentB。 Hence, we can only use mock(TestTarget.class). 因此,我们只能使用模拟(TestTarget.class)。

  2. Since we mocked the TestTarget. 由于我们嘲笑了TestTarget。 There is one thing I missed that we need to use doCallRealMethod().when(targetInstance) to trigger the testInvokation(). 我错过了一件事,我们需要使用doCallRealMethod()。when(targetInstance)来触发testInvokation()。 And this constrains the null declairation of targetInstance. 这限制了targetInstance的空声明。 We need mock() and hook the doCallRealMethod. 我们需要模拟()并挂钩doCallRealMethod。

So the consequence is we need to left @Mock as null without any mock() to let @InjectMocks to handle the shim. 因此,结果是我们需要将@Mock保留为空,而没有任何模拟()以便让@InjectMocks处理垫片。 We just found this is tricky when we use @InjectMocks. 当我们使用@InjectMocks时,我们发现这很棘手。

The problem is with the @InjectMocks definition: 问题在于@InjectMocks定义:

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

The class under test should never be a mock (also why the class keyword). 被测类绝不能是模拟类(也是为什么要使用class关键字)。

Try to use: 尝试使用:

@InjectMocks
private TestTarget targetInstance = new TestTarget();

or simply: 或者简单地:

@InjectMocks
private TestTarget targetInstance;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM