Java单元测试实践-00.目录(9万多字文档+700多测试示例)
https://blog.csdn.net/a82514921/article/details/107969340
1. Answer与未Stub的静态方法
以下主要说明,在对静态方法所在的类执行PowerMockito.mockStatic()方法时,指定的Answer对未被Stub的静态方法的影响。
Answer可以看作执行被Mock/Stub的方法时的回调处理。
2. 未Stub的方法的返回值
当静态方法所在的类通过PowerMockito.mockStatic()进行Mock,对于未被Stub的静态方法,默认情况下,返回值为返回类型对应的默认值。
参考“2. How about some stubbing?”( https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#stubbing )。默认情况下,对于所有有返回值的方法,Mock将根据情况返回null,原始/原始包装器值或空集合。例如,对于int/Integer返回0,对于boolean/Boolean返回false,对于String返回null等。
关于Java基本类型的默认值,可参考“Default Values”( https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html )。
未Stub的方法的各种返回类型对应的默认返回值如下所示:
类型 | 默认返回值 |
---|---|
String | null |
StringBuffer | null |
StringBuilder | null |
byte | 0 |
Byte | 0 |
char | ‘\u0000’ |
Character | 0 |
boolean | false |
Boolean | Boolean.False |
short | 0 |
Short | 0 |
int | 0 |
Integer | 0 |
long | 0L |
Long | 0L |
float | 0F |
Float | 0F |
double | 0D |
Double | 0D |
BigDecimal | null |
自定义类 | null |
List | 元素数量为0的LinkedList对象 |
Map | 元素数量为0的HashMap对象 |
可参考示例TestStMockUnstubbedMethod类。
3. 被Stub方法条件不满足的返回值
对于被Stub的方法,在执行时若参数不满足Stub条件,返回值与未被Stub的方法相同,返回各类型对应的默认值。可参考示例TestStMockStubNotSatisfied类。
4. 未Stub的方法的返回值处理分析
对静态方法所在的类进行Mock时会使用PowerMockito.mockStatic()方法。
查看PowerMockito类的“public static synchronized void mockStatic(Class<?> type, Class<?>… types)”方法,注释为“为类的所有方法启用静态Mock”。在其中调用了“DefaultMockCreator.mock(type, true, false, null, null, (Method[]) null);”方法。
DefaultMockCreator类在org.powermock.api.mockito.internal.mockcreation包中,mock()方法中调用了“MOCK_CREATOR.createMock(type, isStatic, isSpy, delegator, mockSettings, methods);”。
MOCK_CREATOR为DefaultMockCreator类的对象,在DefaultMockCreator类的createMock()方法中,调用了“doCreateMock(type, isStatic, isSpy, delegatorCandidate, mockSettings, methods);”。
在DefaultMockCreator类的doCreateMock()方法中,调用了“createMethodInvocationControl(typeToMock, methods, delegator, mockSettings);”。
在DefaultMockCreator类的createMethodInvocationControl()方法中,调用了“Mockito.mock(type, mockSettings != null ? mockSettings : Mockito.withSettings());”,mockSettings对应PowerMockito.mockStatic()调用DefaultMockCreator.mock()方法时的第5个参数mockSettings,为null,因此调用上述Mockito.mock()方法时的参数为“type, Mockito.withSettings()”。
对应Mockito类的“public static <T> T mock(Class<T> classToMock, MockSettings mockSettings)”方法,调用了“MOCKITO_CORE.mock(classToMock, mockSettings);”,mockSettings参数对应Mockito.withSettings()。
在Mockito类的withSettings()方法中,返回“new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);”。
MockSettingsImpl类在org.mockito.internal.creation中,实现了org.mockito.MockSettings接口,defaultAnswer()方法的注释为“指定未被stub的方法调用的默认answer,传入的参数为未被stub的方法被设置的默认answer”。
RETURNS_DEFAULTS为Mockito类中的变量,等于Answers.RETURNS_DEFAULTS。
Answers枚举中的RETURNS_DEFAULTS值为GloballyConfiguredAnswer类的对象。
GloballyConfiguredAnswer类实现了Answer接口,answer()方法返回“new GlobalConfiguration().getDefaultAnswer().answer(invocation);”。
GlobalConfiguration类的getDefaultAnswer()方法返回“GLOBAL_CONFIGURATION.get().getDefaultAnswer();”,GLOBAL_CONFIGURATION为ThreadLocal<IMockitoConfiguration>,其值通过createConfig()方法设置。在createConfig()方法中,若new ClassPathLoader().loadConfiguration()返回非空则直接使用,若返回为空则使用DefaultMockitoConfiguration对象。
在ClassPathLoader类的loadConfiguration()方法中,判断是否已加载org.mockito.configuration.MockitoConfiguration类,若未加载则返回null,由于默认不存在该类,因此默认会返回null,GlobalConfiguration类的getDefaultAnswer()方法会返回DefaultMockitoConfiguration对象,
在DefaultMockitoConfiguration类的getDefaultAnswer()方法中,返回了ReturnsEmptyValues对象。
ReturnsEmptyValues类在org.mockito.internal.stubbing.defaultanswers包中,实现了Answer接口,在其answer()方法中,对toString与compareTo方法进行了单独处理,其他情况在returnValueFor方法中处理。
在ReturnsEmptyValues类的returnValueFor()方法中,通过Primitives.isPrimitiveOrWrapper()判断若为基本类型或其包装类型,则通过Primitives.defaultValue()方法返回对应的默认值。
Primitives类在org.mockito.internal.util包中,在其Map<Class<?>, Object> PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES变量中保存了基本类型及其包装类型,以及对应的默认值,代码如下:
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Boolean.class, false);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Character.class, '\u0000');
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Byte.class, (byte) 0);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Short.class, (short) 0);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Integer.class, 0);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Long.class, 0L);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Float.class, 0F);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Double.class, 0D);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(boolean.class, false);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(char.class, '\u0000');
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(byte.class, (byte) 0);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(short.class, (short) 0);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(int.class, 0);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(long.class, 0L);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(float.class, 0F);
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(double.class, 0D);
在ReturnsEmptyValues类的returnValueFor方法中,判断不是基本类型,也不是基本类型的包装类型时,处理代码如下:
if (type == Iterable.class) {
return new ArrayList<Object>(0);
} else if (type == Collection.class) {
return new LinkedList<Object>();
} else if (type == Set.class) {
return new HashSet<Object>();
} else if (type == HashSet.class) {
return new HashSet<Object>();
} else if (type == SortedSet.class) {
return new TreeSet<Object>();
} else if (type == TreeSet.class) {
return new TreeSet<Object>();
} else if (type == LinkedHashSet.class) {
return new LinkedHashSet<Object>();
} else if (type == List.class) {
return new LinkedList<Object>();
} else if (type == LinkedList.class) {
return new LinkedList<Object>();
} else if (type == ArrayList.class) {
return new ArrayList<Object>();
} else if (type == Map.class) {
return new HashMap<Object, Object>();
} else if (type == HashMap.class) {
return new HashMap<Object, Object>();
} else if (type == SortedMap.class) {
return new TreeMap<Object, Object>();
} else if (type == TreeMap.class) {
return new TreeMap<Object, Object>();
} else if (type == LinkedHashMap.class) {
return new LinkedHashMap<Object, Object>();
} else if ("java.util.Optional".equals(type.getName())) {
return JavaEightUtil.emptyOptional();
} else if ("java.util.OptionalDouble".equals(type.getName())) {
return JavaEightUtil.emptyOptionalDouble();
} else if ("java.util.OptionalInt".equals(type.getName())) {
return JavaEightUtil.emptyOptionalInt();
} else if ("java.util.OptionalLong".equals(type.getName())) {
return JavaEightUtil.emptyOptionalLong();
} else if ("java.util.stream.Stream".equals(type.getName())) {
return JavaEightUtil.emptyStream();
} else if ("java.util.stream.DoubleStream".equals(type.getName())) {
return JavaEightUtil.emptyDoubleStream();
} else if ("java.util.stream.IntStream".equals(type.getName())) {
return JavaEightUtil.emptyIntStream();
} else if ("java.util.stream.LongStream".equals(type.getName())) {
return JavaEightUtil.emptyLongStream();
}
//Let's not care about the rest of collections.
return null;
总结如下:
使用PowerMockito.mockStatic()方法对类的静态方法进行Mock时,默认情况下会使用Mockito.RETURNS_DEFAULTS作为默认的Answer,对于未被Stub的方法,会返回各类型对应的默认值。
5. 对类进行Mock时设置默认Answer
PowerMockito.mockStatic()方法支持通过传入Answer或MockSettings对象设置默认的Answer,在执行被Mock类的未Stub的静态方法时,由指定的默认Answer进行处理。
当传入Answer对象时,对应PowerMockito类的方法“public static void mockStatic(Class<?> classMock, @SuppressWarnings(“rawtypes”) Answer defaultAnswer)”,示例如下:
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, new CallsRealMethods());
当传入MockSettings对象时,需要通过MockSettings defaultAnswer(Answer defaultAnswer)方法设置默认Answer,对应PowerMockito类的方法“public static void mockStatic(Class<?> classToMock, MockSettings mockSettings)”,示例如下:
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, Mockito.withSettings().defaultAnswer(new CallsRealMethods()));
当类的静态方法被Stub后,在执行该方法时受到对应的Stub控制,不再受默认Answer的影响,可参考示例TestStMockDftAnsCallsRealMethodsA1类。
5.1. 相关的类
5.1.1. Mockito类中的Answer实现类
Mockito类的成员变量包含一些Answer实现类,可作为默认Answer使用,如下所示:
- RETURNS_DEFAULTS
参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#RETURNS_DEFAULTS 。
对于方法未被Stub的被Mock的类,RETURNS_DEFAULTS是其默认的Answer。通常只返回一些空值。
该实现首先尝试使用全局配置,如果没有全局配置,则将使用返回0,空集合,null等值的默认Answer。
- RETURNS_SMART_NULLS
参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#RETURNS_SMART_NULLS 。
该实现在使用遗留代码时可能会有所帮助。未Stub的方法通常返回null。如果代码使用未stub调用返回的对象,可能会出现NullPointerException异常。该Answer实现返回SmartNull而不是null。SmartNull提供比NPE更好的异常消息,因为它指出了调用未Stub方法的代码行,只需单击堆栈跟踪就可以分析。
ReturnsSmartNulls首先尝试返回普通值( 0,空集合,空字符串等 ),然后尝试返回SmartNull。如果返回类型为final,则返回null。
ReturnSmartNulls可能会成为Mockito 4.0.0中的默认返回值策略。
- RETURNS_MOCKS
参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#RETURNS_MOCKS 。
该实现在使用遗留代码时可能会有所帮助。
ReturnMocks首先尝试返回普通值( 0,空集合,空字符串等 ),然后尝试返回Mock对象。如果返回值类型无法Mock( 如是final类型 ),则返回null。
- RETURNS_DEEP_STUBS
参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#RETURNS_DEEP_STUBS 。
常规干净的代码很少需要使用此功能,略。
- CALLS_REAL_METHODS
参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#CALLS_REAL_METHODS 。
该实现在使用遗留代码时可能会有所帮助。使用此实现时,未Stub的方法将委托给真实实现。这是一种创建部分模拟对象的方法,该对象默认情况下会调用真实方法。
- RETURNS_SELF
参考 https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#RETURNS_SELF 。
每当调用返回等于类或超类的类型的方法时,都允许Mock构建器返回自身。
5.1.2. Answers枚举中的Answer实现类
Mockito类中的Answer实现类,均对应Answers枚举中的同名对象。
Answers枚举包含类型为Answer<Object>的变量implementation,对应Answer接口实现类,Answers枚举实现了Answer接口,在answer()方法中调用了implementation的answer()方法并返回,因此Answers的枚举值可以作为对应的Answer实现类使用,Answers枚举的枚举类型及枚举值对应的Answer实现类如下所示:
Answers枚举类型 | Answer实现类 |
---|---|
RETURNS_DEFAULTS | GloballyConfiguredAnswer |
RETURNS_SMART_NULLS | ReturnsSmartNulls |
RETURNS_MOCKS | ReturnsMocks |
RETURNS_DEEP_STUBS | ReturnsDeepStubs |
CALLS_REAL_METHODS | CallsRealMethods |
RETURNS_SELF | TriesToReturnSelf |
以上Answer实现类均在org.mockito.internal.stubbing.defaultanswers包中。
5.1.3. answers包中的Answer实现类
在org.mockito.internal.stubbing.answers包中存在一些Answer实现类,如下所示。
- AnswersWithDelay
延迟指定的时间后,返回指定的Answer的返回值。
- CallsRealMethods
该实现在使用遗留代码时可能会有所帮助。使用此实现时,未Stub的方法将委托给真实实现。这是一种创建部分模拟对象的方法,该对象默认情况下会调用真实方法。在调用真实方法时,通过InvocationOnMock invocation的callRealMethod()方法实现。
- ClonesArguments
注释中说明为TODO,略。
- DoesNothing
该实现什么也不做,在answer()方法中返回null。该实现不支持通过new创建实例,需要使用doesNothing()方法获得实例。
- Returns
Returns类在构造时需要传入一个用户指定的对象Object value,在answer()方法中会返回value对象。
Mockito.when()方法的返回类型为OngoingStubbing接口,BaseStubbing类实现了OngoingStubbing接口。
在BaseStubbing类的thenReturn()方法中,返回“thenAnswer(new Returns(value));”,使用了Returns类。
- ReturnsArgumentAt
返回传递参数中指定下标的参数值。argumentIndex表示调用的参数数组中的索引。如果此数字等于-1,则返回最后一个参数。
- ReturnsElementsOf
返回集合的元素。永远返回最后一个元素。当需要返回元素集合时,该功能可能会很有用。
- ThrowsException
总是抛出相同的Throwable对象的Answer实现类。
5.2. 执行真实方法
使用Mockito.CALLS_REAL_METHODS、Answers.CALLS_REAL_METHODS、CallsRealMethods等作为默认Answer,可以使被Mock的静态方法所在的类的未被Stub的方法默认执行真实方法。在调用PowerMockito.mockStatic()方法时,可以直接使用以上Answer,或使用Mockito.withSettings().defaultAnswer()获得默认Answer为以上指定Answer的MockSettings。
示例如下,可参考示例TestStMockDftAnsCallsRealMethodsA1、TestStMockDftAnsCallsRealMethodsA2、TestStMockDftAnsCallsRealMethodsA3、TestStMockDftAnsCallsRealMethodsWS1、TestStMockDftAnsCallsRealMethodsWS2、TestStMockDftAnsCallsRealMethodsWS3类。
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, Mockito.CALLS_REAL_METHODS);
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, Answers.CALLS_REAL_METHODS);
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, new CallsRealMethods());
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, Mockito.withSettings().defaultAnswer(Mockito.CALLS_REAL_METHODS));
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, Mockito.withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, Mockito.withSettings().defaultAnswer(new CallsRealMethods()));
5.3. 抛出异常
使用ThrowsException作为默认Answer,可以使被Mock的静态方法所在的类的未被Stub的方法默认抛出指定异常,ThrowsException在创建时需要指定需要抛出的异常对象,如下所示,可参考示例TestStMockDftAnsThrowsExpt类。
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, new ThrowsException(new RuntimeException(TestConstants.MOCKED)));
5.4. 什么也不做
使用DoesNothing作为默认Answer,可以使被Mock的静态方法所在的类的未被Stub的方法什么也不做,DoesNothing需要使用doesNothing()方法返回对象实例,如下所示:
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, DoesNothing.doesNothing());
使用DoesNothing作为默认Answer,不同类型的返回值,除了数字相关基本类型返回0,boolean返回false外,其他类型均返回null,如下所示:
返回类型 | 返回值 |
---|---|
String | null |
StringBuffer | null |
StringBuilder | null |
byte | 0 |
Byte | null |
char | ‘\u0000’ |
Character | null |
boolean | false |
Boolean | null |
short | 0 |
Short | null |
int | 0 |
Integer | null |
long | 0L |
Long | null |
float | 0F |
Float | null |
double | 0D |
Double | null |
BigDecimal | null |
自定义类 | null |
List | null |
Map | null |
可参考示例TestStMockDftAnsDoesNothing类。
6. Mockito.when()操作导致真实方法被执行或抛出异常
以下针对静态公有非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语法。
在对类执行PowerMockito.mockStatic()操作时,若默认Answer指定为执行真实方法,当使用Mockito.when().then…()方法执行Stub时,会执行真实方法。如以下示例中,执行Mockito.when(TestStaticPublicNonVoid2.testString())方法时,真实方法会执行,可参考示例TestStMockSECallsRealMethods类。
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, new CallsRealMethods());
Mockito.when(TestStaticPublicNonVoid2.testString(Mockito.anyString())).thenReturn(TestConstants.FLAG1);
在对类执行PowerMockito.mockStatic()操作时,若默认Answer指定为抛出异常,当使用Mockito.when().then…()方法执行Stub时,会抛出指定的异常。如以下示例中,执行Mockito.when(TestStaticPublicNonVoid2.testString())方法会抛出异常RuntimeException,可参考示例TestStMockSEThrowsExpt类。
PowerMockito.mockStatic(TestStaticPublicNonVoid2.class, new ThrowsException(new RuntimeException(TestConstants.MOCKED)));
Mockito.when(TestStaticPublicNonVoid2.testString(Mockito.anyString())).thenReturn(TestConstants.FLAG1);
6.1. Mockito.when()操作执行真实方法或抛出异常的原因分析
使用Mockito.when().then…()对静态公有非void方法进行Stub时,可以分解为以下三步:
-
执行原始方法,参数使用Stub条件,获取返回值;
-
针对返回值执行Mockito.when()方法,获得OngoingStubbing对象
-
执行OngoingStubbing的then…()方法,设置Stub操作
例如对静态方法TestStaticPublicNonVoid2.testString()进行Stub,当参数为任意字符串时,返回字符串TestConstants.FLAG1对应的代码可以分解为以下步骤:
// 分解前
Mockito.when(TestStaticPublicNonVoid2.testString(Mockito.anyString())).thenReturn(TestConstants.FLAG1);
// 分解后
// 执行原始方法
String str = TestStaticPublicNonVoid2.testString(Mockito.anyString());
// 执行Mockito.when()方法,获得OngoingStubbing对象
OngoingStubbing ongoingStubbing = Mockito.when(str);
// 执行then...()方法,设置Stub操作
ongoingStubbing.thenReturn(TestConstants.FLAG2);
当执行PowerMockito.mockStatic方法指定默认Answer为执行真实方法时,以上执行原始方法的步骤会执行真实方法,可参考示例TestStMockSECallsRealMethods类;
当执行PowerMockito.mockStatic方法指定默认Answer抛出异常时,以上执行原始方法的步骤会抛出异常,可参考示例TestStMockSEThrowsExpt类;
当执行PowerMockito.mockStatic方法指定默认Answer为默认的返回空值的Answer时,以上执行原始方法的步骤不会执行真实方法,可参考示例TestStMockSEReturnsDefaults类;
当执行PowerMockito.mockStatic方法指定默认Answer什么也不做时,以上执行原始方法的步骤不会执行真实方法,可参考示例TestStMockSEDoesNothing类。
可以看到,Mockito.when()操作执行真实方法或抛出异常的原因,是Mockito.when()方法中指定的原始方法会被执行,有可能确实执行真实方法,或抛出异常。
6.2. 避免Stub操作执行真实方法或抛出异常
PowerMockito.do…().when()方法与Mockito.when().then…()方法功能类似,使用PowerMockito.do…().when()方法进行Stub时,不会执行真实方法,在有需要时可以使用PowerMockito.do…().when()方法替代Mockito.when().then…()方法进行Stub。
对静态方法使用PowerMockito.do…().when()方法进行Stub的语法为“PowerMockito.do…().when(静态方法所在的类.class, 静态方法名, Stub参数条件)”,示例如下:
PowerMockito.doReturn(TestConstants.MOCKED).when(TestStaticPublicNonVoid2.class, "testString", TestConstants.FLAG1);
Mockito.when().then…()方法及对应的PowerMockito.do…().when()方法如下所示:
then…()方法 | 对应的do…()方法 |
---|---|
Mockito.when().thenReturn() | PowerMockito.doReturn().when() |
Mockito.when().thenThrow() | PowerMockito.doThrow().when() |
Mockito.when().thenAnswer() | PowerMockito.doAnswer().when() |
Mockito.when().thenCallRealMethod() | PowerMockito.doCallRealMethod().when() |
可参考示例TestStMockSEThrowsExpt、TestStMockSECallsRealMethods类。
若使用PowerMockito.when(静态方法所在的类.class, 静态方法名, Stub参数条件).then…()方法进行Stub,无法避免执行真实方法或抛出异常,因为PowerMockito.when()方法中会调用“Mockito.when(Whitebox.<T>invokeMethod(clazz, methodToExpect, arguments))”,“Whitebox.<T>invokeMethod”方法会执行真实方法。