基于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);
    }
}
相关推荐
xlsw_1 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹2 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫2 小时前
泛型(2)
java
超爱吃士力架3 小时前
邀请逻辑
java·linux·后端
南宫生3 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石3 小时前
12/21java基础
java
李小白663 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp3 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶4 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb