Java单元测试实践-00.目录(9万多字文档+700多测试示例)
https://blog.csdn.net/a82514921/article/details/107969340
1. Answer与未Stub的Spring组件方法
1.1. 未Stub的方法的返回值
对于Spring的@Component组件的Mock对象未Stub的方法,返回值为返回类型对应的默认值。与未Stub的静态方法的返回值情况类似。可参考示例TestSpMockUnstubbedMethod1类。
1.2. 未Stub的方法的返回值处理分析
生成Spring的@Component组件的Mock对象时会使用Mockito.mock()方法。
查看Mockito类的“public static <T> T mock(Class<T> classToMock)”方法,在其中调用了“public static <T> T mock(Class<T> classToMock, MockSettings mockSettings)”方法。后续处理可参考被Mock类的未Stub的静态方法的返回值处理分析。
1.3. 被Stub方法条件不满足的返回值
对于Spring的@Component组件的Mock对象被Stub的方法,在执行时若参数不满足Stub条件,返回值与未被Stub的方法相同,返回对应类型的默认值。可参考示例TestSpMockStubNotSatisfied1类。
1.4. 设置未Stub的方法的默认Answer
Mockito.mock()方法支持通过传入Answer或MockSettings对象设置默认的Answer,在执行Mock对象未Stub的方法时,由指定的默认Answer进行处理。
在调用Mockito.mock()方法方法时,若传入参数Answer对象,对应Mockito类的方法“public static <T> T mock(Class<T> classToMock, Answer defaultAnswer)”,示例如下:
Mockito.mock(TestPublicNonVoidService1.class, new CallsRealMethods());
在调用Mockito.mock()方法方法时,若传入参数MockSettings对象,需要通过defaultAnswer设置默认Answer,对应Mockito类的方法“public static <T> T mock(Class<T> classToMock, MockSettings mockSettings)”,示例如下:
Mockito.mock(TestPublicNonVoidService1.class, Mockito.withSettings().defaultAnswer(new CallsRealMethods()));
当Mock对象的非静态方法被Stub后,在执行该方法时受到对应的Stub控制,不再受默认Answer的影响,可参考示例TestSpMockDftAnsCallsRealMethodsImpl类test3方法。
1.4.1. 执行真实方法
使用Mockito.CALLS_REAL_METHODS、Answers.CALLS_REAL_METHODS、CallsRealMethods等作为默认Answer,可以使Mock对象未被Stub的非静态方法执行真实方法。在调用Mockito.mock()方法时,可以直接使用以上Answer,或使用Mockito.withSettings().defaultAnswer()方法及以上Answer,示例如下。
Mockito.mock(TestPublicNonVoidService1.class, Mockito.CALLS_REAL_METHODS);
Mockito.mock(TestPublicNonVoidService1.class, Answers.CALLS_REAL_METHODS);
Mockito.mock(TestPublicNonVoidService1.class, new CallsRealMethods());
Mockito.mock(TestPublicNonVoidService1.class, Mockito.withSettings().defaultAnswer(Mockito.CALLS_REAL_METHODS));
Mockito.mock(TestPublicNonVoidService1.class, Mockito.withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
Mockito.mock(TestPublicNonVoidService1.class, Mockito.withSettings().defaultAnswer(new CallsRealMethods()));
对于Spring的@Component组件接口的Mock对象,当设置默认Answer执行真实方法时,执行未Stub的非静态方法时,不会执行真实方法。可参考示例TestSpMockDftAnsCallsRealMethodsInterface类。
对于Spring的@Component组件实现类的Mock对象,当设置默认Answer执行真实方法时,执行未Stub的非静态方法时,会执行真实方法。可参考示例TestSpMockDftAnsCallsRealMethodsImpl类test1方法。
对于Spring的@Component组件实现类的Mock对象,使用执行真实方法的默认Answer,当执行真实方法时,若使用了需要注入的成员变量,由于需要注入的成员变量为null,会出现空指针异常。可参考示例TestSpMockDftAnsCallsRealMethodsImpl类test2方法。
在实际使用中,Spring的@Component组件实现类的Mock对象使用意义不大,因为其中需要注入的成员变量均为null,无法正常使用。若需要使Spring的@Component组件可以根据需要执行指定操作,或执行真实方法,可以使用Spy对象。
1.4.2. 抛出异常
使用ThrowsException作为默认Answer,可以使Mock对象未被Stub的非静态方法抛出指定异常,ThrowsException在创建时需要指定需要抛出的异常对象。如下所示,可参考示例TestSpMockDftAnsThrowsExpt1类。
TestPublicNonVoidService1 testPublicNonVoidService1 = Mockito.mock(TestPublicNonVoidService1.class, new ThrowsException(new RuntimeException(TestConstants.MOCKED)));
1.4.3. 什么也不做
使用DoesNothing作为默认Answer,可以使Mock对象未被Stub的非静态方法什么也不做,DoesNothing需要使用doesNothing()方法返回对象实例。如下所示,可参考示例TestSpMockDftAnsDoesNothing1类。
TestPublicNonVoidService1 testPublicNonVoidService1 = Mockito.mock(TestPublicNonVoidService1.class, DoesNothing.doesNothing());
1.4.4. 使用@Mock注解的answer参数
通过@Mock注解的answer参数,也可以指定Spring的@Component组件的Mock对象的默认Answer,answer参数类型为Answers,可以使用Answers枚举中的RETURNS_DEFAULTS等枚举值。当需要使Mock对象能够执行真实方法时,@Mock注解需要针对实现类设置。示例如下,可参考示例TestSpMockDftAnsAnnotationInterface、TestSpMockDftAnsAnnotationImpl类。
@Mock(answer = Answers.CALLS_REAL_METHODS)
private TestPublicNonVoidService1Impl testPublicNonVoidService1;
1.4.5. MockSettings的stubOnly属性
参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/MockSettings.html#stubOnly-- 。通过MockSettings接口的stubOnly()方法,可以指定生成的Mock对象仅用于对方法进行Stub,不记录方法调用,可以节省内存,但不允许进行调用验证(Mockito.verify())。默认情况下,stubOnly为false。
使用Mockito.mock()方法时,指定stubOnly属性的示例如下:
List stubOnly = Mockito.mock(List.class, withSettings().stubOnly());
@Mock注解中的stubOnly属性,也以起到相同的作用,使用@Mock注解时,指定stubOnly属性的示例如下:
@Mock(stubOnly = true)
private TestPublicNonVoidService1Impl testPublicNonVoidService1;
1.4.6. 委托方法调用
参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/AdditionalAnswers.html#delegatesTo-java.lang.Object- 。
使用AdditionalAnswers类的delegatesTo()方法可以生成一个将方法调用直接转发给被委托代表的Answer。被委托代表可以与Mock对象的类型相同或不同,若类型不相同,被委托代表中需要有与Mock对象相同的方法,否则会出现异常。
例如使用Mockito.mock()方法获得了A类的Mock对象,使用AdditionalAnswers.delegatesTo()方法可以指定A类的Mock对象,也可以使用与A类具有相同方法(方法名称、参数数量、类型、返回值均相同)的B类的对象。
对于使用常规Spy API难以进行Mock或Spy的Spy/部分Mock对象,使用AdditionalAnswers类的delegatesTo()方法很有帮助。可在以下场景使用:
- 具有接口的final类;
- 已被自定义代理的对象(如AOP、Mybatis的Mapper对象等);
- 具有finalize方法的特殊对象( 即避免执行两次 )
使用Spy对象可以进行verify,使用delegatesTo不支持verify。
在使用AdditionalAnswers类的delegatesTo()方法生成Answer时,需要指定被委托代表。
使用Mockito.mock()方法生成Mock对象时,可以指定上述Answer,生成方法调用被委托的Mock对象并返回,AdditionalAnswers.delegatesTo()方法指定的对象为被委托代表。示例如下,可参考示例TestSpMockDelegatesTo1类
TestServiceA1 testServiceA1 = Mockito.mock(TestServiceA1.class, AdditionalAnswers.delegatesTo(new
TestServiceA1Delegate()));
被委托代表可以使用Spring的@Component组件,代码需要保存在test模块中(防止对main模块产生影响),并在Spring配置文件的context:component-scan参数中添加对象的包名(或将包名设置为满足main模块中的Spring包扫描路径),使Spring对测试代码中的被委托代表以@Component组件的形式被扫描到。示例如下,可参考示例TestSpMockDelegatesTo2、TestServiceA1Delegate类。
@Autowired
private TestServiceA1Delegate testServiceA1Delegate;
TestServiceA1 testServiceA1 = Mockito.mock(TestServiceA1.class, AdditionalAnswers.delegatesTo(testServiceA1Delegate));
方法调用被委托的Mock对象,未被Stub的方法调用会被转发给被委托代表的对应的相同方法( 方法名称、参数数量、类型、返回值均相同 ),即调用被委托的Mock对象的方法时,会调用被委托代表的对应方法并返回。当间接调用被委托的方法时也能生效。例如以上生成的Mock对象testServiceA1,当调用其方法时,会调用被委托代表testServiceA1Delegate对象相同的方法。
方法调用被委托的Mock对象,若被调用的方法在被委托代表中不存在匹配的方法,在执行时会出现异常。异常示例如下,可参考示例TestSpMockDelegatesToWrong类。
org.mockito.exceptions.base.MockitoException
Methods called on mock must exist in delegated instance.
When calling: public abstract java.lang.String com.adrninistrator.service.TestServiceA1.test1(java.lang.String) on mock: testServiceA1
no such method was found.
Check that the instance passed to delegatesTo() is of the correct type or contains compatible methods
(delegate instance had type: TestApplicationListener)
方法调用被委托的Mock对象,被Stub的方法会执行Stub指定的操作,不会将调用转发给被委托代表。间接调用时Stub也生效。可参考示例TestSpMockDelegatesToStub2类。
在对方法调用被委托的Mock对象进行Stub时,不应使用Mockito.when()方法,会导致被委托代表的方法被执行。应使用Mockito.do…().when()方法进行Stub,避免执行委托代表的方法。可参考示例TestSpMockDelegatesToStub1、TestSpMockDelegatesToStub2类。
1.5. Mockito.when()操作导致真实方法被执行或抛出异常
关于Mockito.when()操作导致真实方法被执行或抛出异常的说明,在对静态方法进行Mock的部分已进行了说明,但存在一些区别。
以下针对非静态公有非void方法进行分析(对于非静态方法,Mock.when().then…()不支持公有非void之外的方法)。
参考“CALLS_REAL_METHODS”( https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#CALLS_REAL_METHODS )。使用when(mock.getSomething()).thenReturn(fakeValue)语法进行Stub会执行真实方法,建议使用doReturn语法。
在对接口执行Mockito.mock()操作时,若默认Answer指定为执行真实方法,当使用Mockito.when().then…()方法执行Stub时,不会执行真实方法。
在对实现类执行Mockito.mock()操作时,若默认Answer指定为执行真实方法,当使用Mockito.when().then…()方法执行Stub时,会执行真实方法。如以下示例中,执行Mockito.when(testPublicNonVoidService1ImplMock.test1())方法时,真实方法会执行:
private TestPublicNonVoidService1 testPublicNonVoidService1ImplMock;
testPublicNonVoidService1ImplMock = Mockito.mock(TestPublicNonVoidService1Impl.class, new CallsRealMethods());
Mockito.when(testPublicNonVoidService1ImplMock.test1(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
以上可参考示例TestSpMockSECallsRealMethods类。
在对接口或实现类执行Mockito.mock()操作时,若默认Answer指定为抛出异常,当使用Mockito.when().then…()方法执行Stub时,会抛出指定的异常。如以下示例中,执行Mockito.when(testPublicNonVoidService1Mock.test1())方法会抛出异常RuntimeException,可参考示例TestSpMockSEThrowsExpt类。
private TestPublicNonVoidService1 testPublicNonVoidService1Mock;
testPublicNonVoidService1Mock = Mockito.mock(TestPublicNonVoidService1.class,
new ThrowsException(new RuntimeException(TestConstants.MOCKED)));
assertThrows(RuntimeException.class, () ->
Mockito.when(testPublicNonVoidService1Mock.test1(Mockito.anyString())).thenReturn(TestConstants.MOCKED)
);
在对接口或实现类执行Mockito.mock()操作时,若默认Answer指定为默认的返回空值的Answer,当使用Mockito.when().then…()方法执行Stub时,不会执行真实方法,可参考示例TestSpMockSEReturnsDefaults类。
在对接口或实现类执行Mockito.mock()操作时,若默认Answer指定什么也不做,当使用Mockito.when().then…()方法执行Stub时,不会执行真实方法,可参考示例TestSpMockSEDoesNothing类。
1.5.1. Mockito.when()操作执行真实方法或抛出异常的原因分析
关于Mockito.when()操作执行真实方法或抛出异常的原因,已在前文静态方法部分进行过分析,不再重复。
1.5.2. 避免Stub操作执行真实方法或抛出异常
Mockito.do…().when()方法与Mockito.when().then…()方法功能类似,使用Mockito.do…().when()方法进行Stub时,不会执行真实方法,在有需要时可以使用Mockito.do…().when()方法替代Mockito.when().then…()方法进行Stub。
对非静态方法使用Mockito.do…().when()方法进行Stub的语法为“Mockito.do…().when(Mock对象).非静态方法(Stub参数条件)”,示例如下:
Mockito.doReturn(TestConstants.MOCKED).when(testPublicNonVoidService1ImplMock).test1(Mockito.anyString());
Mockito.when().then…()方法及对应的Mockito.do…().when()方法如下所示:
then…()方法 | 对应的do…()方法 |
---|---|
Mockito.when().thenReturn() | Mockito.doReturn().when() |
Mockito.when().thenThrow() | Mockito.doThrow().when() |
Mockito.when().thenAnswer() | Mockito.doAnswer().when() |
Mockito.when().thenCallRealMethod() | Mockito.doCallRealMethod().when() |
可参考示例TestSpMockSECallsRealMethods、TestSpMockSEThrowsExpt类。