Bootstrap

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()方法

	@CheckReturnValue
    public static <T> T mock(Class<T> classToMock) {
        return mock(classToMock, withSettings());
    }

发现除了我们传过来要mock的类以外,还有一个参数withSettings(),继续查看withSettings()

	@CheckReturnValue
    public static MockSettings withSettings() {
        return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);
    }

继续查看defaultAnswer,发现其实RETURNS_DEFAULTS是一个Answer实现

	@Override
    public MockSettings defaultAnswer(Answer defaultAnswer) {
        this.defaultAnswer = defaultAnswer;
        if (defaultAnswer == null) {
            throw defaultAnswerDoesNotAcceptNullParameter();
        }
        return this;
    }

查看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.
     */
    public static final Answer<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.
 */
public enum Answers implements Answer<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(new GloballyConfiguredAnswer()),

    /**
     * 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(new ReturnsSmartNulls()),

    /**
     * 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(new ReturnsMocks()),

    /**
     * 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(new ReturnsDeepStubs()),

    /**
     * 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(new CallsRealMethods()),

    /**
     * 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(new TriesToReturnSelf());

    private final Answer<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> .
     */
    @Deprecated
    public Answer<Object> get() {
        return this;
    }

    public Object answer(InvocationOnMock invocation) throws Throwable {
        return implementation.answer(invocation);
    }
}

一共有6个对象,暂时先放过。查看GloballyConfiguredAnswer()
org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer

	public Object answer(InvocationOnMock invocation) throws Throwable {
        return new GlobalConfiguration().getDefaultAnswer().answer(invocation);
    }

org.mockito.internal.configuration.GlobalConfiguration

	public Answer<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,发现终于找到头了🤣

public class ReturnsEmptyValues implements Answer<Object>, Serializable {

    private static final long serialVersionUID = 1998191268711234347L;

    /* (non-Javadoc)
     * @see org.mockito.stubbing.Answer#answer(org.mockito.invocation.InvocationOnMock)
     */
    public Object answer(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();
            }
        } else if (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 interface
            return invocation.getMock() == invocation.getArgument(0) ? 0 : 1;
        }

        Class<?> returnType = invocation.getMethod().getReturnType();
        return returnValueFor(returnType);
    }

    Object returnValueFor(Class<?> type) {
        if (Primitives.isPrimitiveOrWrapper(type)) {
            return Primitives.defaultValue(type);
            // new instances are used instead of Collections.emptyList(), etc.
            // to avoid UnsupportedOperationException if code under test modifies returned
            // collection
        } else 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();
        } else if ("java.time.Duration".equals(type.getName())) {
            return JavaEightUtil.emptyDuration();
        } else if ("java.time.Period".equals(type.getName())) {
            return JavaEightUtil.emptyPeriod();
        }

        // Let's not care about the rest of collections.
        return null;
    }
}

两个if条件
isToStringMethod应该是处理toString的,isCompareToMethod应该是处理compareTo的,get方法进不去,本次不关注,有兴趣的可以了解一下
咱们就看returnValueFor,根据对mock对象使用的方法的返回类型,进行不同处理。上面贴出来的代码,都是处理集合类型的(set、list、map),返回一个对应的空集合。我们看一下第一个if条件

	if (Primitives.isPrimitiveOrWrapper(type)) {
        return Primitives.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进行判断

	public static boolean isPrimitiveOrWrapper(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允许生成器模拟在调用返回与类或父类相同类型的方法时返回自身。

使用方法:

	public static <T> T mock(Class<T> classToMock, Answer defaultAnswer) {
        return mock(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。不过能知道可以进行处理,也是不错的收获。

;