如何使用Mockito完成单元测试

概述

单元测试通过屏蔽复杂的依赖关系,集中于某个具体的类的逻辑进行测试,可以有效在代码编写初期发现逻辑问题。本文使用Mockito技术实现全部场景下单元测试编写,配合集成测试能够达到更好的效果。
单元测试编写有如下要求:
1、单元测试覆盖率要达到100%。
2、不引入额外的,不需要的类。

依赖引入

<!--测试依赖-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <exclusions>
    <exclusion>
      <artifactId>junit</artifactId>
      <groupId>junit</groupId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.8.2</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>junit-jupiter</artifactId>
  <exclusions>
    <exclusion>
      <artifactId>mockito-core</artifactId>
      <groupId>org.mockito</groupId>
    </exclusion>
    <exclusion>
      <artifactId>junit-jupiter-api</artifactId>
      <groupId>org.junit.jupiter</groupId>
    </exclusion>
  </exclusions>
  <version>2.20.0</version>
</dependency>
<!--mockito的依赖项-->
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-inline</artifactId>
  <version>4.4.0</version>
  <exclusions>
    <exclusion>
      <artifactId>mockito-core</artifactId>
      <groupId>org.mockito</groupId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <exclusions>
    <exclusion>
      <artifactId>byte-buddy</artifactId>
      <groupId>net.bytebuddy</groupId>
    </exclusion>
    <exclusion>
      <artifactId>byte-buddy-agent</artifactId>
      <groupId>net.bytebuddy</groupId>
    </exclusion>
  </exclusions>
  <version>4.4.0</version>
</dependency>

<dependency>
  <groupId>net.bytebuddy</groupId>
  <artifactId>byte-buddy</artifactId>
  <version>1.12.9</version>
</dependency>
<dependency>
  <groupId>net.bytebuddy</groupId>
  <artifactId>byte-buddy-agent</artifactId>
  <version>1.12.9</version>
  <scope>test</scope>
</dependency>
<!--mybatisplus测试为仅加载mybatisplus引入的依赖项-->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter-test</artifactId>
  <version>3.5.2</version>
  <scope>test</scope>
</dependency>
<!--数据库测试引入的内嵌数据库-->
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <version>2.1.214</version>
  <scope>test</scope>
</dependency>
<!--pojo geter seter方法引入的组件-->
<dependency>
  <groupId>com.openpojo</groupId>
  <artifactId>openpojo</artifactId>
  <version>0.9.1</version>
</dependency>

<!--本地查看单元测试进度引入的组件-->
<dependency>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.8</version>
</dependency>

测试场景

普通业务代码

由于 Spring 技术的广泛使用,大部分服务内的类都是交由 Spring 容器进行管理的,一个类往往依赖很多其他类,在编写单元测试时,屏蔽其他类是一个十分麻烦的过程。使用 Mocikito 可以使用类似于 Spring 依赖注入的语法,减少工作量。

/**
 * @ExtendWith注解使用MockitoExtension,会识别@InjectMocks和@Mock等注解
 * @MockitoSetting注解,设置为Strictness.LENIENT,忽略没被使用的mock方法,避免重复代码
 */
@ExtendWith({MockitoExtension.class})
@MockitoSettings(strictness = Strictness.LENIENT)
public MockitoTest {
	/**
	 * @InjectMocks注解的对象是需要测试的类
	 * @Mock注解的对象会被加入到context中,自动组装给@InjectMocks注解的对象
	 */
	@InjectMocks
	private TestClass testClass;
	@Mock
	private DependencyClass dependencyClass;

	/**
	 * 在每个测试方法前进行初始化
	 */
	@BeforeEach
	public void init() {
		Mockito.doAnswer(invocation -> {
            Object param = invocation.getArgument(0);
            return "";
       }).when(testClass).test(Mockito.any());
	}
	/**
	 * 执行具体测试场景
	 */
	 @Test
	 public void test() {
	 	testClass.test("test")
	 }
}

使用静态方法的业务

在测试类中往往会存在调用了其他类静态方法的情况,有时静态方法的返回值会影响测试流程,所以需要对静态方法的返回值进行设定。对静态方法类的 mock 不能使用 @Mock 注解完成,需要额外定义一个 MockedStatic 对象处理,需要注意的是,静态类的 mock 的作用域是当前线程。

	/**
	 * 对需要进行 mock 的静态类进行声明
	 */
	MockedStatic<StaticClass> staticClass;
	
	@BeforeEach
	public void init() {
		staticClass= Mockito.mockStatic(StaticClass.class);
        staticClass.when(() -> StaticClass.staticMethod()).thenReturn("10");
	}
	
	/**
	 * 当前线程下所有单元测试执行完成后,需要将静态 mock 资源释放
	 */
	 @AfterEach
    public void after() {
        staticClass.close();
    }

使用特定对象的业务

在具体的测试方法中,往往会存在使用构造器创建新对象的情况,后续还会调用此对象的特定方法。在单元测试中,这种在方法内直接创建的对象往往不受我们控制,如果需要在对象创建时返回一个我们定义好的 mock 对象,可以使用构造器 mock 对象。

	/**
	 * 对需要进行 mock 的类进行声明
	 */
	MockedConstruction<ConstructionClass> constructionClass;
	
	@BeforeEach
	public void init() {
		constructionClass= Mockito.mockConstruction(constructionClass.class, (mock, context) -> {
            doAnswer(invocation -> {
                return null;
            }).when(mock).test();
        });
	}
	
	/**
	 * 当前线程下所有单元测试执行完成后,需要将构造器 mock 资源释放
	 */
	 @AfterEach
    public void after() {
        constructionClass.close();
    }

controller 层接口单元测试

controller 层涉及到 web 服务的启动,很多拦截器相关的逻辑都需要加载 web 环境才能实现,所以尽量在保证引入最小依赖的前提下,完成接口测试。

/**
 * @ExtendWith({SpringExtension.class}) 使用 SpringExtension 来管理容器
 * @WebMvcTest 只加载测试 controller 依赖的对象
 * @ContextConfiguration 指定启动类
 * @MockBeans 指定 mock 类
 */
@ExtendWith({SpringExtension.class})
@WebMvcTest(controllers = TestController.class)
@ContextConfiguration(classes={ApplicationTest.class})
@MockBeans({@MockBean(TestService.class)})
public class ControllerTest {
	/**
	 * 装配 MVC 环境
	 */
	@Autowired
    private MockMvc mockMvc;
    @Autowired
    TestService testService;
	
	@Test
    public void controllerTest() {
        TestRequest requset = new TestRequest ();
        String url = "/test";
        //存在则成功
        ResultActions resultAction = this.mockMvc.perform(post(url)
                .content(JSONObject.toJSONString(requset))
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                .andDo(print());
        resultAction.andExpect(status().isOk())
                .andExpect(content().string(containsString("处理成功")));
    }
}

同一场景多值单元测试

我们在编写单元测试时,往往会出现在同一个场景下需要测试不同值的情况,比如在测试“字符串转整型”功能时,需要测试字符串为空、字符串非空、字符串非数字等情况。出了传值有区别外,其他场景完全一致。为了简化单元测试编写,可以使用 @ParameterizedTest 和 @ValueSource 注解,完成多值测试。

	@ParameterizedTest
    @ValueSource(strings = {null, "hello", "1"})
    void testStringToInt(String str) {
        Integer.parseInt(str);
    }

总结

Mockito 是一个功能十分强大的单元测试框架。灵活搭配上述场景中描述的不同的特性,几乎可以覆盖全部的单元测试需求。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值