项目场景:
最近在搞单元测试,在逐步摸索中,记录一些小坑、小知识点。
本文为 mock出来的对象的属性值与get方法取到的属性值不一致的问题。
问题描述
例如:如下场景,needCheck为我mock的对象
NeedCheck needCheck =Mockito.mock(NeedCheck.class);
发现最终的结果不是我断言的结果,就进行debug调试,如下,很奇怪为什么能进入这个if中
最开始我没有注意到
Byte chklevel = needCheck.getChklevel();
这一行代码。就一直很奇怪:needCheck对象的maxchklevel为null,needCheck.getMaxchklevel()也应该是null,前面chklevel 为byte(0),这俩怎么可能相等呢???
后来发给同事看,同事发现
Byte chklevel = needCheck.getChklevel();
这一行代码,chklevel 为byte(0),但是needCheck对象的chklevel为null。发现这个之后,我就去看了一下needCheck.getMaxchklevel()的值
果然也是byte(0),这样它们相等就是对的,果然代码是不会骗人的😂,但是问题来了,为什么属性为null但是get取到的值却不是null呢?
原因分析:
很自然的想到了,mock出来的对象被处理了默认值,查看这个Mockito.mock()方法
@CheckReturnValuepublicstatic<T>Tmock(Class<T> classToMock){returnmock(classToMock,withSettings());}
发现除了我们传过来要mock的类以外,还有一个参数withSettings(),继续查看withSettings()
@CheckReturnValuepublicstaticMockSettingswithSettings(){returnnewMockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);}
继续查看defaultAnswer,发现其实RETURNS_DEFAULTS是一个Answer实现
@OverridepublicMockSettingsdefaultAnswer(Answer defaultAnswer){this.defaultAnswer = defaultAnswer;if(defaultAnswer ==null){throwdefaultAnswerDoesNotAcceptNullParameter();}returnthis;}
查看Answer类,注释表明Answer用于处理mock对象的返回值。继续查看RETURNS_DEFAULTS,发现它是在Mockito类中定义的,等于Answers.RETURNS_DEFAULTS;
/**
* The default <code>Answer</code> of every mock <b>if</b> the mock was not stubbed.
*
* Typically it just returns some empty value.
* <p>
* {@link Answer} can be used to define the return values of unstubbed invocations.
* <p>
* This implementation first tries the global configuration and if there is no global configuration then
* it will use a default answer that returns zeros, empty collections, nulls, etc.
*/publicstaticfinalAnswer<Object> RETURNS_DEFAULTS =Answers.RETURNS_DEFAULTS;
继续找Answers.RETURNS_DEFAULTS,发现Answers是一个枚举类
/**
* Enumeration of pre-configured mock answers
* <p>
* You can use it to pass extra parameters to @Mock annotation, see more info here: {@link Mock}
* <p>
* Example:
* <pre class="code"><code class="java">
* @Mock(answer = RETURNS_DEEP_STUBS) UserProvider userProvider;
* </code></pre>
* <b>This is not the full list</b> of Answers available in Mockito. Some interesting answers can be found in org.mockito.stubbing.answers package.
*/publicenumAnswersimplementsAnswer<Object>{/**
* The default configured answer of every mock.
*
* <p>Please see the {@link org.mockito.Mockito#RETURNS_DEFAULTS} documentation for more details.</p>
*
* @see org.mockito.Mockito#RETURNS_DEFAULTS
*/RETURNS_DEFAULTS(newGloballyConfiguredAnswer()),/**
* An answer that returns smart-nulls.
*
* <p>Please see the {@link org.mockito.Mockito#RETURNS_SMART_NULLS} documentation for more details.</p>
*
* @see org.mockito.Mockito#RETURNS_SMART_NULLS
*/RETURNS_SMART_NULLS(newReturnsSmartNulls()),/**
* An answer that returns <strong>mocks</strong> (not stubs).
*
* <p>Please see the {@link org.mockito.Mockito#RETURNS_MOCKS} documentation for more details.</p>
*
* @see org.mockito.Mockito#RETURNS_MOCKS
*/RETURNS_MOCKS(newReturnsMocks()),/**
* An answer that returns <strong>deep stubs</strong> (not mocks).
*
* <p>Please see the {@link org.mockito.Mockito#RETURNS_DEEP_STUBS} documentation for more details.</p>
*
* @see org.mockito.Mockito#RETURNS_DEEP_STUBS
*/RETURNS_DEEP_STUBS(newReturnsDeepStubs()),/**
* An answer that calls the real methods (used for partial mocks).
*
* <p>Please see the {@link org.mockito.Mockito#CALLS_REAL_METHODS} documentation for more details.</p>
*
* @see org.mockito.Mockito#CALLS_REAL_METHODS
*/CALLS_REAL_METHODS(newCallsRealMethods()),/**
* An answer that tries to return itself. This is useful for mocking {@code Builders}.
*
* <p>Please see the {@link org.mockito.Mockito#RETURNS_SELF} documentation for more details.</p>
*
* @see org.mockito.Mockito#RETURNS_SELF
*/RETURNS_SELF(newTriesToReturnSelf());privatefinalAnswer<Object> implementation;Answers(Answer<Object> implementation){this.implementation = implementation;}/**
* @deprecated as of 2.1.0 Use the enum-constant directly, instead of this getter. This method will be removed in a future release<br>
* E.g. instead of <code>Answers.CALLS_REAL_METHODS.get()</code> use <code>Answers.CALLS_REAL_METHODS</code> .
*/@DeprecatedpublicAnswer<Object>get(){returnthis;}publicObjectanswer(InvocationOnMock invocation)throwsThrowable{return implementation.answer(invocation);}}
一共有6个对象,暂时先放过。查看GloballyConfiguredAnswer()
org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer
publicObjectanswer(InvocationOnMock invocation)throwsThrowable{returnnewGlobalConfiguration().getDefaultAnswer().answer(invocation);}
org.mockito.internal.configuration.GlobalConfiguration
publicAnswer<Object>getDefaultAnswer(){return GLOBAL_CONFIGURATION.get().getDefaultAnswer();}
org.mockito.stubbing.Answer.IMockitoConfiguration
Answer<Object>getDefaultAnswer();
找到它的实现类org.mockito.configuration.DefaultMockitoConfiguration
public Answer<Object> getDefaultAnswer() {
return new ReturnsEmptyValues();
}
查看ReturnsEmptyValues,发现终于找到头了🤣
publicclassReturnsEmptyValuesimplementsAnswer<Object>,Serializable{privatestaticfinallong serialVersionUID =1998191268711234347L;/* (non-Javadoc)
* @see org.mockito.stubbing.Answer#answer(org.mockito.invocation.InvocationOnMock)
*/publicObjectanswer(InvocationOnMock invocation){if(isToStringMethod(invocation.getMethod())){Object mock = invocation.getMock();MockName name =MockUtil.getMockName(mock);if(name.isDefault()){return"Mock for "+MockUtil.getMockSettings(mock).getTypeToMock().getSimpleName()+", hashCode: "+ mock.hashCode();}else{return name.toString();}}elseif(isCompareToMethod(invocation.getMethod())){// see issue 184.// mocks by default should return 0 if references are the same, otherwise some other// value because they are not the same. Hence we return 1 (anything but 0 is good).// Only for compareTo() method by the Comparable interfacereturn invocation.getMock()== invocation.getArgument(0)?0:1;}Class<?> returnType = invocation.getMethod().getReturnType();returnreturnValueFor(returnType);}ObjectreturnValueFor(Class<?> type){if(Primitives.isPrimitiveOrWrapper(type)){returnPrimitives.defaultValue(type);// new instances are used instead of Collections.emptyList(), etc.// to avoid UnsupportedOperationException if code under test modifies returned// collection}elseif(type ==Iterable.class){returnnewArrayList<Object>(0);}elseif(type ==Collection.class){returnnewLinkedList<Object>();}elseif(type ==Set.class){returnnewHashSet<Object>();}elseif(type ==HashSet.class){returnnewHashSet<Object>();}elseif(type ==SortedSet.class){returnnewTreeSet<Object>();}elseif(type ==TreeSet.class){returnnewTreeSet<Object>();}elseif(type ==LinkedHashSet.class){returnnewLinkedHashSet<Object>();}elseif(type ==List.class){returnnewLinkedList<Object>();}elseif(type ==LinkedList.class){returnnewLinkedList<Object>();}elseif(type ==ArrayList.class){returnnewArrayList<Object>();}elseif(type ==Map.class){returnnewHashMap<Object,Object>();}elseif(type ==HashMap.class){returnnewHashMap<Object,Object>();}elseif(type ==SortedMap.class){returnnewTreeMap<Object,Object>();}elseif(type ==TreeMap.class){returnnewTreeMap<Object,Object>();}elseif(type ==LinkedHashMap.class){returnnewLinkedHashMap<Object,Object>();}elseif("java.util.Optional".equals(type.getName())){returnJavaEightUtil.emptyOptional();}elseif("java.util.OptionalDouble".equals(type.getName())){returnJavaEightUtil.emptyOptionalDouble();}elseif("java.util.OptionalInt".equals(type.getName())){returnJavaEightUtil.emptyOptionalInt();}elseif("java.util.OptionalLong".equals(type.getName())){returnJavaEightUtil.emptyOptionalLong();}elseif("java.util.stream.Stream".equals(type.getName())){returnJavaEightUtil.emptyStream();}elseif("java.util.stream.DoubleStream".equals(type.getName())){returnJavaEightUtil.emptyDoubleStream();}elseif("java.util.stream.IntStream".equals(type.getName())){returnJavaEightUtil.emptyIntStream();}elseif("java.util.stream.LongStream".equals(type.getName())){returnJavaEightUtil.emptyLongStream();}elseif("java.time.Duration".equals(type.getName())){returnJavaEightUtil.emptyDuration();}elseif("java.time.Period".equals(type.getName())){returnJavaEightUtil.emptyPeriod();}// Let's not care about the rest of collections.returnnull;}}
两个if条件
isToStringMethod应该是处理toString的,isCompareToMethod应该是处理compareTo的,get方法进不去,本次不关注,有兴趣的可以了解一下
咱们就看returnValueFor,根据对mock对象使用的方法的返回类型,进行不同处理。上面贴出来的代码,都是处理集合类型的(set、list、map),返回一个对应的空集合。我们看一下第一个if条件
if(Primitives.isPrimitiveOrWrapper(type)){returnPrimitives.defaultValue(type);// new instances are used instead of Collections.emptyList(), etc.// to avoid UnsupportedOperationException if code under test modifies returned// collection}
接着看Primitives.isPrimitiveOrWrapper(type),PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES是一个map,containsKey进行判断
publicstaticbooleanisPrimitiveOrWrapper(Class<?> type){return PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.containsKey(type);}
PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES里都有什么呢?
static{
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);}
这样就清楚了,根据返回类型,返回各种默认值。
Byte会返回(byte) 0,所以出现属性值为null,get值不为null的情况。
另外可以看到String类型是没有处理的,所以走到最后,会返回null。
接着我们看一下Answers枚举类的六个对象,都是什么用途,怎么用的
来到org.mockito.Answers 居然又让我们去看org.mockito.Mockito😂
Answers解释(机器翻译)RETURNS_DEFAULTS此实现首先尝试全局配置,如果没有全局配置,则将使用返回零、空集合、空值等的默认答案。RETURNS_SMART_NULLS在处理遗留代码时,此实现可能会有所帮助。未创建的方法通常返回null。如果您的代码使用未经subbed调用返回的对象,则会得到NullPointerException。这个实现返回SmartNull,而不是null。RETURNS_MOCKS在处理遗留代码时,此实现可能会有所帮助。ReturnsMocks首先尝试返回普通值(零、空集合、空字符串等),然后尝试返回mock。如果无法模拟返回类型(例如为final),则返回纯
null
。RETURNS_DEEP_STUBS大概意思就是适用于链式调用。person.getAddress(anyString()).getStreet().getName()CALLS_REAL_METHODS当使用此实现时,未建模的方法将委托给真正的实现。这是一种创建默认情况下调用真实方法的部分模拟对象的方法。RETURNS_SELF允许生成器模拟在调用返回与类或父类相同类型的方法时返回自身。
使用方法:
publicstatic<T>Tmock(Class<T> classToMock,Answer defaultAnswer){returnmock(classToMock,withSettings().defaultAnswer(defaultAnswer));}
NeedCheck needCheck =Mockito.mock(NeedCheck.class,Mockito.CALLS_REAL_METHODS);
PS:也可以自己实现一个Answer ,按如上方式进行mock。
注:使用的是springboot2.4.8
junit以及mockito为spring-boot-starter-test引入的,对应的org.junit.jupiter版本为5.7.2,org.mockito版本为3.6.28
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
总结:
本文只是研究了一下为什么属性值和get值取到的结果不一致。mock对象还是使用默认的就好,暂时没有什么使用场景,包括其他几种Answers。不过能知道可以进行处理,也是不错的收获。
版权归原作者 阿狸尬多 所有, 如有侵权,请联系我们删除。