基于mockito做单元测试

1.简介

  • 配合断言使用(杜绝System.out)
  • 可重复执行
  • 不依赖环境
  • 不会对数据产生影响
  • Spring的上下文环境不是必备的
  • 一般都配合mock类框架对数据库进行隔离

mock类使用场景:

要进行测试的方法存在外部依赖(DB,Redis,第三方接口),为了专注于对该方法的逻辑进行测试,就希望能隔离出来外部依赖,避免外部依赖成为单测的阻塞项,一般单测都是在service进行测试

创建mock对象的三种方法:

java 复制代码
/**
 * 创建mock对象的第一种方法
 */
@ExtendWith(MockitoExtension.class)
public class InitMockOrSpyMethod1 {
    @Mock
    private UserService mockUserService;
    @Spy
    private UserService spyUserServices;

    @Test
    public void test1(){
        System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
        System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
        // spy对象是另一种类型的mock对象
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
    }

}
java 复制代码
/**
 * 创建mock对象的第二种方法
 */
public class InitMockOrSpyMethod2 {
    private UserService mockUserService;
    private UserService spyUserServices;

    @BeforeEach
    public void init(){
        mockUserService = Mockito.mock(UserService.class);
        spyUserServices = Mockito.spy(UserService.class);
    }

    @Test
    public void test1(){
        System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
        System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
        // spy对象是另一种类型的mock对象
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
    }
}
java 复制代码
/**
 * 创建mock对象的第三种方法
 */
public class InitMockOrSpyMethod3 {
    @Mock
    private UserService mockUserService;
    @Spy
    private UserService spyUserServices;

    @BeforeEach
    public void init(){
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void test1(){
        System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
        System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
        // spy对象是另一种类型的mock对象
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
    }
}

2.mock和spy对象

mock对象:

  • 方法插桩:执行插桩逻辑
  • 方法不插桩:返回mock对象的默认值
  • 作用对象:类吗,接口

spy对象:

  • 方法插桩:执行插桩逻辑
  • 方法不插桩:调用真实方法
  • 作用对象:类吗,接口

3.参数匹配

java 复制代码
@Test
public void test1() {
    UserDO userDO = new UserDO();
    userDO.setId(1L);
    doReturn("nothing").when(mockUserService).selectNameById(userDO.getId());
    // 只有执行Mockito.doReturn才执行插桩
    System.out.println(mockUserService.selectNameById(userDO.getId()));
    // userDO2对象不进行插桩,返回默认值
    UserDO userDO2 = new UserDO();
    userDO2.setId(2L);
    System.out.println(mockUserService.selectNameById(userDO2.getId()));
}
java 复制代码
    /**
     * 此时我只想拦截UserDO类型的任意对象
     * ArgumentMatchers.*拦截任意类型
     */
    @Test
    public void test2() {
        doReturn("nothingName").when(mockUserService).selectNameById(ArgumentMatchers.anyLong());
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        System.out.println(mockUserService.selectNameById(userDO.getId()));
        UserDO userDO2 = new UserDO();
        userDO2.setId(2L);
        System.out.println(mockUserService.selectNameById(userDO2.getId()));
    }

方法插桩

指定调用某个方法时的行为,达到相互隔离的效果

  • 返回指定值
java 复制代码
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class StubTest {
    @Mock
    private List<String> mockList;
    /**
     * 指定返回值
     */
    @Test
    public void test1() {
        // 方法一
        doReturn("zero").when(mockList).get(0);
        // 如果返回值不相等,则测试不通过
        Assertions.assertEquals("zero", mockList.get(0));

        //方法二
        when(mockList.get(1)).thenReturn("one");
        Assertions.assertEquals("one", mockList.get(1));
    }
}
  • void返回值方法插桩
java 复制代码
/**
  * void 返回值插桩
  */
@Test
public void test2() {
    // 调用mockList.clear()什么也没做
    doNothing().when(mockList).clear();
    mockList.clear();
    verify(mockList, times(1)).clear();
}
  • 插桩的两种形式

    • when(obj.someMethod.thenXXX):用于mock对象
    • doXXX():用于mock/spy对象

    区别在于不插桩时doXXX应用与spy对象时会调用真实方法

    java 复制代码
    public void test4() {
        when(mockUserServiceImpl.getNumber()).thenReturn(99);
        System.out.println("mockUserServiceImpl.getNumber():"+mockUserServiceImpl.getNumber());
        doReturn(99).when(spyUserServiceImpl).getNumber();
        System.out.println("spyUserServiceImpl.getNumber() = " + spyUserServiceImpl.getNumber());
    }
  • 抛异常

java 复制代码
/**
     * 断言异常
     */
    @Test
    public void test3() {
        doThrow(RuntimeException.class).when(mockList).get(anyInt());
        try{
            mockList.get(10000);//此处get(10000)为空
            Assertions.fail();
        }catch (RuntimeException e){
            // 断言表达式为真
            Assertions.assertTrue(e instanceof RuntimeException);
        }
    }
  • 多次插桩
java 复制代码
/**
     * 多次插桩
     */
    @Test
    public void test4() {
        // 第一次调用返回1,第二次返回2,第三次返回3以及以后得调用都返回三
        when(mockList.size()).thenReturn(1)
                .thenReturn(2)
                .thenReturn(3);
        Assertions.assertEquals(1,mockList.size());
        Assertions.assertEquals(2,mockList.size());
        Assertions.assertEquals(3,mockList.size());
        Assertions.assertEquals(4,mockList.size());
    }
  • thenAnswer
java 复制代码
/**
     * thenAnswer:实现指定的插桩逻辑
     */
    @Test
    public void test5() {
        // 此处不管传入什么参数,都返回100*3
        when(mockList.get(anyInt())).thenAnswer(new Answer<String>() {
            /**
             * 泛型是要返回插桩的返回值类型
             * @param invocationOnMock
             * @return
             * @throws Throwable
             */
            @Override
            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
                Integer argument = invocationOnMock.getArgument(0, Integer.class);
                return String.valueOf(argument*100);
            }
        });
        String s = mockList.get(3);
        Assertions.assertEquals("300",s);
    }
  • 执行真正的原始方法
java 复制代码
/**
     * 执行真正的原始方法
     */
    @Test
    public void test6() {
        when(mockUserServiceImpl.getNumber()).thenCallRealMethod();
        int number = mockUserServiceImpl.getNumber();
        Assertions.assertEquals(1,number);
    }
  • verify的使用
java 复制代码
    /**
     * 指定方法返回几次
     */
    @Test
    public void test3() {
        mockUserService.add("lily", "123123", new ArrayList<>());
        // 验证add方法被调用了1次
        Mockito.verify(mockUserService, Mockito.times(1))
                .add("lily", "123123", new ArrayList<>());
        // 要么全部使用ArgumentMatchers,不能一半使用参数,一半使用ArgumentMatchers
        Mockito.verify(mockUserService, Mockito.times(1))
                .add(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyList());
    }

4.@InjectMocks注解

  • 作用:如果@InjectMocks声明的变量需要用到mock,spy对象,mockito会自动使用当前类里的mock或者spy成员进行an类型或者名字注入
  • 原理:构造器注入,setter注入,字段反射注入
java 复制代码
@ExtendWith(MockitoExtension.class)
public class InjectMocksTest {

    /**
     * 被InjectMocks标注的属性,必须是实现类,因为Mockito会创建InjectMocks注解的类的实例,
     * 并注入到被InjectMocks标注的属性中,默认创建的对象就是没有经过Mockito处理过的对象,因此
     * 配合@Spy注解,变成可以调用默认方法的mock对象
     */
    @InjectMocks
    @Spy
    private UserServiceImpl userService;

    @Mock
    private UserFeatureService userFeatureService;

    @Test
    public void test() {
        int number = userService.getNumber();
        Assertions.assertEquals(1, number);
    }
}
相关推荐
wuminyu3 小时前
专家视角看Java字节码加载与存储指令机制
java·linux·c语言·jvm·c++
callJJ4 小时前
Spring Data Redis 两种编程模型详解:同步 vs 响应式
java·spring boot·redis·python·spring
wbs_scy5 小时前
Linux线程同步与互斥(三):线程同步深度解析之POSIX 信号量与环形队列生产者消费者模型,从原理到源码彻底吃透
java·开发语言
jinanwuhuaguo6 小时前
(第三十三篇)五月的文明奠基:OpenClaw 2026.5.2版本的文明级解读
android·java·开发语言·人工智能·github·拓扑学·openclaw
xmjd msup7 小时前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
952367 小时前
SpringBoot统一功能处理
java·spring boot·后端
Lyyaoo.7 小时前
优惠券秒杀业务分析
java·开发语言
消失的旧时光-19437 小时前
统一并发模型:线程、Reactor、协程本质是一件事(从线程到协程 · 第6篇·终章)
java·python·算法
勿忘初心12217 小时前
Java 国密 SM4 加密工具类实战(Hutool + BouncyCastle)|企业级数据加密 + 兼容 JDK8
java·数据安全·数据加密·后端开发·企业级开发·国密 sm4
庞轩px7 小时前
第8篇:原子类与CAS底层原理——无锁并发的实现
java·cas·乐观锁·aba·无锁编程·自旋