暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

springboot使用mockito进行单元测试

原创 我为啥没洁癖 2023-12-12
867

依赖

spring-boot-starter-test依赖中包含junit5和mokito核心包。springboot的2.6.15版本的mokito-core的版本是4.0.0.

mockito-inline用于静态成员的mock,从mokito的3.4.0版本开始支持。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.6.15</version>
    <scope>test</scope>
</dependency>
<dependency>
	<groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.0.0</version>
    <scope>test</scope>
</dependency>

注解作用

@SpringBootTest

@SpringBootTest引入spring的运行时环境时使用,一般的单元测试使用mock的数据即可,不需要连接数据库、redis等。

@Test

@Test标识方法为一个单元测试

@Mock

全模拟一个虚拟数据,类似数据库连接等不好构造的数据,然后配合Mokito的API来打桩,当什么方法调用时返回什么值。

@Mock模拟的出来的对象在打桩后的动作只会在当前test方法中生效。

@Spy

部分模拟一个虚拟数据,也可以配合Mokito的API来打桩,当什么方法调用时返回什么值。

@InjectMocks

@InjectMocks模拟对象并将@Mock、@Spy模拟的对象自动注入到自己的成员变量中。

单测基类

无Spring

在单独测试一些需要Mock数据但不需要使用Spring提供的上下文相关的功能时,只需要初始化mock数据即可。单测类继承这个BaseTest基类,实现Mock对象的初始化,避免重复代码。

jupiter包下

public abstract class BaseTest { private AutoCloseable closeable; @BeforeEach void beforeEach() { closeable = openMocks(this); } @AfterEach void releaseMocks() throws Exception { closeable.close(); } }

有Spring

在单独测试一些需要Mock数据且需要使用Spring提供的上下文相关的功能时,比如测API接口。

import com.xrj.security.UserUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.mockito.MockedStatic; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @SpringBootTest public abstract class BaseMvcTest { protected MockMvc mockMvc; @Autowired private WebApplicationContext webApplicationContext; @BeforeEach void before() { mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); } }

Mokito打桩

实例方法打桩

打桩:模拟对象的行为返回预先设定好的值或做出预设的行为。常用的打桩逻辑有when和doNothing两种:

  • when(xxx.doSome()).thenReturn(yyy)

    xxx对象调用doSome方法时返回yyy,只有有返回值的方法可以使用这个。

  • doNothing().when(xxx).doSome()

    xxx对象调用doSome方法时什么也不做,只有没有返回值的方法可以使用这个。

比如现在有个SomeService的Bean,有PlanDao和UserService两个成员变量,现在是测试SomeService.doSome()方法,其内依次调用了planDao.findDetailuserService.saveAction,测试时只想测doSome内的逻辑而不想findDetailsaveAction走真实的逻辑,那么此时用下面打桩的方法就可以在someService.doSome()调用时,让对应调用findDetailsaveAction的地方进行按照我们预设的逻辑返回,这样就达到目的了。

class SomeTest extends BaseTest{ @Mock private PlanDao planDao; @Mock private UserService userService; @InjectMocks private SomeService someService; @test void testDoSome(){ ... List<Plan> list = Lists.newArrayList(); //当planDao调用findDetail方法参数为1时返回list列表,list是自行初始化的 when(planDao.findDetail(1)).thenReturn(list); //当userService调用saveAction方法时什么也不做 doNothing().when(userService).saveAction(action); //调用被测方法 Some actual = someService.doSome(); ... } }

静态方法打桩

在早期的mokito中不支持静态打桩的,需要配合PowerMock依赖。

从mokito的3.4.0版本后,新增了mockito-inline扩展包用于支持静态方法的打桩,方便对静态方法进行打桩。

<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.0.0</version> <scope>test</scope> </dependency>

UserUtils类下有一个getCurrentUserId的静态方法,此时就给这个方法进行打桩,让其返回结果为1

class SomeTest extends BaseTest{ @test void testDoSome(){ ... //mock类 MockedStatic<UserUtils> mockedUserUtils = mockStatic(UserUtils.class); //打桩,getCurrentUserId方法是一个静态方法 mockedUserUtils.when(UserUtils::getCurrentUserId).thenReturn(1); //调用被测方法 Some actual = someService.doSome(); ... } }

MockedStatic底层继承了AutoCloseable,使用完毕需要close。所以可以将静态工具类的初始化和清理放在单测基础类中。

public abstract class BaseTest { private AutoCloseable closeable; protected static MockedStatic<UserUtils> mockedUserUtils; @BeforeAll static void setUp() { mockedUserUtils = mockStatic(UserUtils.class); } @AfterAll static void clear(){ mockedUserUtils.close(); } @BeforeEach void beforeEach() { closeable = openMocks(this); } @AfterEach void releaseMocks() throws Exception { closeable.close(); } }

结果验证

断言

Junit5中的断言由Assertions类提供,类似Junit4中的Assert。一般是用于判断被测试方法的返回值是否符合预期。

//断言预期值和实际值结果相等 Assertions.assertEquals(expected, actual); //断言方法调用后抛出指定异常 Assertions.assertThrows(MyExecption.class, () -> someService.doSome());

行为验证

Junit5中的行为验证功能由verifyAPI提供,验证被测试方法中的细节行为是否符合预期。

//验证在被测试的方法中planDao.findDetail(1)调用过一次。 verify(planDao,times(1)).findDetail(1);
class SomeTest extends BaseTest{ @Mock private PlanDao planDao; @Mock private UserService userService; @InjectMocks private SomeService someService; @test void testDoSome(){ Some exepected = new Some(); List<Plan> list = Lists.newArrayList(); //当planDao调用findDetail方法参数为1时返回list列表,list是自行初始化的 when(planDao.findDetail(1)).thenReturn(list); //当userService调用saveAction方法时什么也不做 doNothing().when(userService).saveAction(action); //调用被测方法 Some actual = someService.doSome(); //断言实际返回值和预期值一致 Assertions.assertEquals(expected, actual); //验证在被测试的方法中planDao.findDetail(1)调用过一次。 verify(planDao,times(1)).findDetail(1); } }

MockMvc

MockMvc就是单测基类需要使用Spring上下文的场景,使用前需要初始化Spring上下文。

@MockBean

在MockMvc测试时是模拟http调用,此时测试controller接口时可以使用@MockBean注解注入service实例,将Service实例方法进行打桩测试,避免单元测试影响到数据库。

测试API

SomeController中有接口/doSome可以接收参数name/doSome接口中调用someService.findSome()并将返回值作为接口响应值。

class SomeControllerTest extends BaseMvcTest { @MockBean private SomeService someService; @Test void testDoSome() throws Exception { //打桩 Some expected = new Some(); Mockito.when(someService.findSome()).thenReturn(expected)); //发起mvc调用 MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders .get("/doSome") .accept(MediaType.APPLICATION_JSON) .characterEncoding(StandardCharsets.UTF_8) .param("name", "test") ).andExpect(MockMvcResultMatchers.status().isOk()).andReturn(); //验证返回值 String resStr = mvcResult.getResponse().getContentAsString(); Some actual = JSON.parseObject(resStr, Some.class); Assertions.assertEquals(expected, actual); } }
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论