0


Mockito对象的属性与get方法

项目场景:

最近在搞单元测试,在逐步摸索中,记录一些小坑、小知识点。
本文为 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 &#064;Mock annotation, see more info here: {@link Mock}
 * <p>
 * Example:
 * <pre class="code"><code class="java">
 *   &#064;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。不过能知道可以进行处理,也是不错的收获。


本文转载自: https://blog.csdn.net/qq_42445433/article/details/124627846
版权归原作者 阿狸尬多 所有, 如有侵权,请联系我们删除。

“Mockito对象的属性与get方法”的评论:

还没有评论