Mockito 基础 愉快单元测试之旅

本文介绍了Mockito作为Java单元测试框架的基础用法,包括静态导入简化语法、mock和spy的区别及用法,以及在Spring Boot中的应用。强调了在Spring Boot中使用Mockito时的注意事项,如避免使用@SpyBean和@Transaction注解引发的问题,推荐使用Spring Test框架结合Mockito进行测试。同时提醒读者注意Spring Context Cache可能导致的测试干扰,并提供了避免此类问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Mockito 基础

Mockito 是实现mock技术的java框架。

mock技术主要解决非常难创建的对象进行模拟,或者是在测试阶段无法创建的对象进行模拟。

常见的模拟如:RPC远程调用,数据库,Minio,Kafka等

相关资源

框架官网网站:

Mockito framework site

本文只讲基本使用,不会设计到所有Mockito的命令,具体其他命令参考官网Document

基本使用

1.通过静态导入来让mokito命令写的简洁

(后面的所有讲述内容都以简写来描述)

在import的地方加入:

import static org.mockito.Mockito.*;

加入之后mockito语法的格式:

doReturn("Hello").when(someClass).sayHello()

没加入上面的import语句的格式:

Mockito.doReturn("Hello").when(someClass).sayHello()
2.基本命令mock和spy

2.1 mock和spy的区别:

mock: 封装一个空壳的对象,如果没有使用mock指定返回值,则直接返回null

spy: 封装真实对象,可以选择是否执行原始功能,没有指定的mock函数会按照正常流程走

2.2 基本使用语法
2.2.1 mock使用例子:
// 新建一个空壳对象(任何方法都不会被真实执行)
TestClass mock = mock(TestClass.class);

// 例如 我需要当mock对象执行Print方法的时候返回10
// 写法1
when(mock.Print()).thenReturn(10);
// 写法2
doReturn(10).when(mock).Print();

// 执行测试
System.out.println(mock.Print());
2.2.2 spy使用例子:
// 创建一个真实对象,因为spy会保留真实对象功能
TestClass testClass = new TestClass();

// 使用spy创建一个testClass的间谍对象
TestClass spy = spy(testClass);

// 不会执行原始功能写法
doReturn(10).when(spy).Print();

// 会执行原始功能的写法
when(spy.Print()).thenReturn(10);

// 测试运行
System.out.println(spy.Print());
3. mockito在Spring Boot中的使用

Spring Boot中使用Mokito有2个注解可以方便的创建Mock对象和Spy对象, 并将其注入到Spring容器,如果是Single的对象则会替换。 (个人不推荐使用,本人在测试的时候遇到非常多的bug问题。后面会讲到我的解决方法和推荐的方法)

// 这一个注解等同于mock(TestClass.class) 然后再将mock对象注入到Spring容器
@MockBean
private TestClass testClass;

// 这一个注解等同于spy(testClass) 首先从容器中拿到TestClass的对象然后对其进行spy然后注入到Spring容器
@SpyBean
private TestClass testClass;

关于SpyBean的使用案例

UserDao.java代码

@Component
public class UserDao {
    public void printUser(){
        System.out.println("打印一个User");
    }
}

UserService.java代码

@Service
public class UserService {
    @Resource
    private UserDao userDao;
    
    public void printUser(){
        userDao.printUser();
    }
}

测试代码:

import static org.mockito.Mockito.*;

@SpringBootTest(classes = {MainApplication.class})
public class UserServiceTest {

    // 创建一个空壳对象并注入到容器
    @MockBean
    private UserDao userDao;

    @Resource
    private UserService userService;

    @BeforeEach
    void before(){
        // 我需要让userDao什么都不执行, 即不会输出内容
        doNothing().when(userDao).printUser();
    }

    @Test
    void printUserTest(){
        userService.printUser();
    }
}

测试结果不会输出 打印一个User

4.使用Spring Test 框架+ mockito原生来测试 (本人推荐)

使用例子代码:

UserDao.java代码

@Component
public class UserDao {
    public void printUser(){
        System.out.println("打印一个User");
    }
}

UserService.java代码

@Service
public class UserService {
    @Resource
    private UserDao userDao;
    
    public void printUser(){
        userDao.printUser();
    }
}

测试代码:

import static org.mockito.Mockito.*;

@SpringBootTest(classes = {MainApplication.class})
public class UserServiceTest2 {

    @Resource
    private UserService userService;

    @BeforeEach
    void before() {
        // 创建空壳对象
        UserDao userDaoMock = mock(UserDao.class);
        // 我需要让userDao什么都不执行, 即不会输出内容
        doNothing().when(userDaoMock).printUser();

        // 讲userService内部变量userDao指向这个新建的mock对象
        ReflectionTestUtils.setField(userService, "userDao", userDaoMock);
    }

    @Test
    void printUserTest() {
        userService.printUser();
    }
}

测试结果不会输出 打印一个User

5.本人遇到的坑
5.1 使用@SpyBean包裹Service对象时候,如果Service里面有@Transaction注解则会抛出异常

解决方法:

使用我上面说的 使用Spring Test 框架+ mockito原生来测试

同时不使用以下来包裹

spy(service)

而使用

mock(service.class, AdditionalAnswers.delegatesTo(具体Service实例))
5.2 使用时应要注意Spring Context Cache 的问题

在同一个Test测试文件中,对容器内容Bean的所有修改会影响本测试文件的其他测试用例。

@Resource
private IUserService userService;

@Test
void selectTest() {
    // 创建Mock对象
    UserDao userDaoMock = mock(UserDao.class);
    doReturn(new ArraryList<User>()).when(userDaoMock).selectAllUser();
    
    // 使用反射注入到userService实例类
    ReflectionTestUtils.setField(userService,"userDao",userDaoMock);
    
    // 调用
    userService.SelectAllUser();
}

@Test
void selectTest2(){
    userService.SelectAllUser();
}

注:第一个测试用例采用mock来mock指定对象,但是第二个测试用例没有mock

然后我们使用Idea运行测试用例,如果每个都单独运行,是不会有影响的。

但是如果运行整个文件的测试用例,会先运行selectTest测试用例,然后运行selectTest2测试用例。这会导致selectTest2也有selectTest的mock设置。

如果不希望有这个问题则需要按如下写:

@Resource
private IUserService userService;

@Test
@DirtiesContext  // 新增注解,用于标记此测试会影响容器内容,需要运行完成后处理缓存
void selectTest() {
    // 创建Mock对象
    UserDao userDaoMock = mock(UserDao.class);
    doReturn(new ArraryList<User>()).when(userDaoMock).selectAllUser();
    
    // 使用反射注入到userService实例类
    ReflectionTestUtils.setField(userService,"userDao",userDaoMock);
    
    // 调用
    userService.SelectAllUser();
}

@Test
void selectTest2(){
    userService.SelectAllUser();
}

使用@DirtiesContext可以清除spring 容器缓存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值