Mockito 中的 Answer 参数

Mockito 中的 Answer 参数

在使用Mockito中的mock方法时,有一个重载方法,支持传入Answer类型的参数,表示没有打桩的方法的处理策略(default answer for unstubbed methods)。常见的选项如下:

参数值描述
CALLS_REAL_METHODS调用真实方法
RETURNS_DEEP_STUBS允许深度模拟。即允许在mock对象中使用链式调用
RETURNS_DEFAULTS默认值。表示返回默认值,如0,空集合,null等。
RETURNS_MOCKSfirst tries to return ordinary values (zeros, empty collections, empty string, etc.) then it tries to return mocks. If the return type cannot be mocked (e.g. is final) then plain null is returned.
RETURNS_SELF在build模式下返回本身的方法,直接返回自身。
RETURNS_SMART_NULLS智能null,不会报空指针

CALLS_REAL_METHODS

参考代码:

package com.ysx.utils.datetime;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

/**
 * @author youngbear
 * @email youngbear@aliyun.com
 * @date 2024-07-21 23:27
 * @blog <a href="https://blog.csdn.net/next_second">...</a>
 * @github <a href="https://github.com/YoungBear">...</a>
 * @description 日期工具类
 */
public class DateUtils {

    /**
     * 默认格式
     */
    private static final DateTimeFormatter YEAR_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 获取前一天的起始时间 00:00:00
     * 如 2024-07-21 00:00:00
     *
     * @return 格式化日期时间
     */
    public static String getLastDayStartTimePattern() {
        return LocalDate.now().minusDays(1).atTime(LocalTime.MIN).format(YEAR_DATE_TIME_FORMATTER);

    }
}

单元测试:

package com.ysx.utils.datetime;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.time.LocalDate;

/**
 * @author youngbear
 * @email youngbear@aliyun.com
 * @date 2024-07-21 23:34
 * @blog <a href="https://blog.csdn.net/next_second">...</a>
 * @github <a href="https://github.com/YoungBear">...</a>
 * @description unit test for {@link DateUtils}
 */
public class DateUtilsTest {

    @Test
    @DisplayName("不跨月 getLastDayStartTimePattern test")
    public void getLastDayStartTimePatternTest1() {
        LocalDate mockedNow = LocalDate.of(2024, 7, 21);
        try (MockedStatic<LocalDate> mocked = Mockito.mockStatic(LocalDate.class, Mockito.CALLS_REAL_METHODS)) {
            mocked.when(LocalDate::now).thenReturn(mockedNow);
            Assertions.assertEquals("2024-07-20 00:00:00", DateUtils.getLastDayStartTimePattern());
        }
    }

    @Test
    @DisplayName("跨月 getLastDayStartTimePattern test")
    public void getLastDayStartTimePatternTest2() {
        LocalDate mockedNow = LocalDate.of(2024, 7, 1);
        try (MockedStatic<LocalDate> mocked = Mockito.mockStatic(LocalDate.class, Mockito.CALLS_REAL_METHODS)) {
            mocked.when(LocalDate::now).thenReturn(mockedNow);
            Assertions.assertEquals("2024-06-30 00:00:00", DateUtils.getLastDayStartTimePattern());
        }

    }

    @Test
    @DisplayName("跨年 getLastDayStartTimePattern test")
    public void getLastDayStartTimePatternTest3() {
        LocalDate mockedNow = LocalDate.of(2024, 1, 1);
        try (MockedStatic<LocalDate> mocked = Mockito.mockStatic(LocalDate.class, Mockito.CALLS_REAL_METHODS)) {
            mocked.when(LocalDate::now).thenReturn(mockedNow);
            Assertions.assertEquals("2023-12-31 00:00:00", DateUtils.getLastDayStartTimePattern());
        }
    }
}

说明:

这里仅mock静态方法LocalDate::now,所以对于minusDays等其他非静态方法,会调用其真实方法。因为minusDays内部也会调用静态方法LocalDate::ofEpochDay,所以如果不加CALLS_REAL_METHODS参数的话,就会报空指针异常。

其他选项

ClassA:

package com.ysx.utils.mockito;

import java.util.List;

/**
 * @author youngbear
 * @email youngbear@aliyun.com
 * @date 2024-07-22 23:26
 * @blog <a href="https://blog.csdn.net/next_second">...</a>
 * @github <a href="https://github.com/YoungBear">...</a>
 * @description
 */
public class ClassA {

    private String name;

    private int age;

    private List<String> list;

    private ClassC classC;

    public String method1() {
        return "method1";
    }

    public String method2() {
        return "method2";
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public List<String> getList() {
        return list;
    }

    public ClassC getClassC() {
        return classC;
    }
}

ClassB:

package com.ysx.utils.mockito;

import java.util.List;

/**
 * @author youngbear
 * @email youngbear@aliyun.com
 * @date 2024-07-22 23:29
 * @blog <a href="https://blog.csdn.net/next_second">...</a>
 * @github <a href="https://github.com/YoungBear">...</a>
 * @description
 */
public class ClassB {
    private ClassA classA;

    public ClassB(ClassA classA) {
        this.classA = classA;
    }

    public String method1() {
        return classA.method1();
    }

    public String method2() {
        return classA.method2();
    }

    public String getName() {
        return classA.getName();
    }

    public int getAge() {
        return classA.getAge();
    }
    public List<String> getList() {
        return classA.getList();
    }
    public ClassC getClassC() {
        return classA.getClassC();
    }
}

ClassC:

package com.ysx.utils.mockito;

/**
 * @author youngbear
 * @email youngbear@aliyun.com
 * @date 2024-07-23 0:08
 * @blog <a href="https://blog.csdn.net/next_second">...</a>
 * @github <a href="https://github.com/YoungBear">...</a>
 * @description
 */
public class ClassC {
    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

验证程序

ClassBTest:

package com.ysx.utils.mockito;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.exceptions.verification.SmartNullPointerException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

/**
 * @author youngbear
 * @email youngbear@aliyun.com
 * @date 2024-07-22 23:30
 * @blog <a href="https://blog.csdn.net/next_second">...</a>
 * @github <a href="https://github.com/YoungBear">...</a>
 * @description
 */
class ClassBTest {

    @Test
    @DisplayName("不指定Answers")
    void defaultAnswerTest() {
        ClassA mockA = mock(ClassA.class);
        doReturn("method1_X").when(mockA).method1();
        ClassB classB = new ClassB(mockA);
        // 打桩的函数返回打桩的值
        assertEquals("method1_X", classB.method1());
        // 未打桩的函数,返回默认值:String默认为null,int默认为0,集合默认为空集合
        assertNull(classB.method2());
        assertNull(classB.getName());
        assertEquals(0, classB.getAge());
        assertEquals(0, classB.getList().size());
        assertNull(classB.getClassC());
    }

    @Test
    @DisplayName("指定默认Answers:Mockito.RETURNS_DEFAULTS")
    void useDefaultAnswerTest() {
        ClassA mockA = mock(ClassA.class, Mockito.RETURNS_DEFAULTS);
        doReturn("method1_X").when(mockA).method1();
        ClassB classB = new ClassB(mockA);
        // 打桩的函数返回打桩的值
        assertEquals("method1_X", classB.method1());
        // 未打桩的函数,返回默认值:String默认为null,int默认为0,集合默认为空集合
        assertNull(classB.method2());
        assertNull(classB.getName());
        assertEquals(0, classB.getAge());
        assertEquals(0, classB.getList().size());
        assertNull(classB.getClassC());
    }

    @Test
    @DisplayName("指定智能空值Answers:Mockito.RETURNS_SMART_NULLS")
    void useReturnsSmartNullsAnswerTest() {
        ClassA mockA = mock(ClassA.class, Mockito.RETURNS_SMART_NULLS);
        doReturn("method1_X").when(mockA).method1();
        ClassB classB = new ClassB(mockA);
        // 打桩的函数返回打桩的值
        assertEquals("method1_X", classB.method1());
        // 未打桩的函数,返回smart null:String默认为空字符串,int默认为0,集合默认为空集合
        assertEquals("", classB.method2());
        assertEquals("", classB.getName());
        assertEquals(0, classB.getAge());
        assertEquals(0, classB.getList().size());
        assertThrows(SmartNullPointerException.class, () -> classB.getClassC().getName());
        assertThrows(SmartNullPointerException.class, () -> classB.getClassC().getAge());
    }

    @Test
    @DisplayName("指定Answers:Mockito.RETURNS_MOCKS")
    void useReturnsMocksAnswerTest() {
        ClassA mockA = mock(ClassA.class, Mockito.RETURNS_MOCKS);
        doReturn("method1_X").when(mockA).method1();
        ClassB classB = new ClassB(mockA);
        // 打桩的函数返回打桩的值
        assertEquals("method1_X", classB.method1());
        // 未打桩的函数,返回mock对象
        assertEquals("", classB.method2());
        assertEquals("", classB.getName());
        assertEquals(0, classB.getAge());
        assertEquals(0, classB.getList().size());
        assertNotNull(classB.getClassC());
        assertEquals("", classB.getClassC().getName());
        assertEquals(0, classB.getClassC().getAge());
    }

    @Test
    @DisplayName("指定Answers:Mockito.RETURNS_DEEP_STUBS")
    void useReturnsDeepStubsAnswerTest() {
        ClassA mockA = mock(ClassA.class, Mockito.RETURNS_DEEP_STUBS);
        // 在创建模拟(mock)对象时,自动模拟(stub)该对象及其返回的任何嵌套对象
        when(mockA.getClassC().getName()).thenReturn("Name1");
        ClassB classB = new ClassB(mockA);
        // 打桩的函数返回打桩的值
        assertEquals("Name1", classB.getClassC().getName());
    }

    @Test
    @DisplayName("指定Answers:Mockito.CALLS_REAL_METHODS")
    void useCallRealMethodsAnswerTest() {
        ClassA mockA = mock(ClassA.class, Mockito.CALLS_REAL_METHODS);
        doReturn("method1_X").when(mockA).method1();
        ClassB classB = new ClassB(mockA);
        // 打桩的函数返回打桩的值
        assertEquals("method1_X", classB.method1());
        // 未打桩的函数,调用真实方法
        assertEquals("method2", classB.method2());
        assertNull(classB.getName());
        assertEquals(0, classB.getAge());
        assertNull(classB.getList());
        assertNull(classB.getClassC());
    }

    @Test
    @DisplayName("使用spy,类似于:Mockito.CALLS_REAL_METHODS")
    void useSpyTest() {
        ClassA mockA = spy(ClassA.class);
        doReturn("method1_X").when(mockA).method1();
        ClassB classB = new ClassB(mockA);
        // 打桩的函数返回打桩的值
        assertEquals("method1_X", classB.method1());
        // 未打桩的函数,调用真实方法
        assertEquals("method2", classB.method2());
        assertNull(classB.getName());
        assertEquals(0, classB.getAge());
        assertNull(classB.getList());
        assertNull(classB.getClassC());
    }
}

参考

源代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值