美文网首页
SpringBoot中的单元测试及Mockito框架

SpringBoot中的单元测试及Mockito框架

作者: liushiping | 来源:发表于2023-06-11 09:20 被阅读0次

一、在SpringBoot项目中引入单元测试框架

在做系统的自动化持续集成的时候,会要求自动的做单元测试,只有所有的单元测试都跑通了,才能打包构建。单元测试是软件测试的基础,因此单元测试的效果会直接影响到软件的后期测试,最终在很大程度上影响到产品的质量。在SpringBoot项目中,可以通过新增maven依赖将单元测试框架依赖进来:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
   <exclusions>
      <exclusion>
         <groupId>org.junit.vintage</groupId>
         <artifactId>junit-vintage-engine</artifactId>
      </exclusion>
   </exclusions>
</dependency>

在SpringBoot2中,该依赖自动将JUnit5Mockito测试框架依赖进来。

二、JUnit测试框架

JUnit是Java领域经典的测试框架,目前最新版本是JUnit5,采用了Java8的编程风格并且比JUnit4更加健壮和灵活。在开始书写测试代码之前,我们先回顾一下JUnit常用的测试注解。在JUnit4和JUnit5中,注解的写法有些许变化。

特性 JUnit4 JUnit5
声明一个测试方法 @Test @Test
在当前类的所有测试方法执行前要执行的方法 @BeforeClass @BeforeAll
在当前类的所有测试方法执行后要执行的方法 @AfterClass @AfterAll
每个测试方法执行前要执行的方法 @Before @BeforeEach
每个测试方法执行后要执行的方法 @After @AfterEach
忽略某个测试方法或测试类 @Ignore @Disabled
动态测试用例生成工厂 无此特性 @TestFactory
嵌套测试 无此特性 @Nested
标记与过滤 @Category @Tag
注册定制扩展点 无此特性 @ExtendWith

三、Mockito测试框架

Mockito框架可以创建和配置mock对象来模拟对象的行为,与JUnit结合使用,使用Mockito简化了具有外部依赖的类的测试开发。Mockito测试框架可以帮助我们模拟HTTP请求,从而达到在服务端测试目的。因为其不会真的去发送HTTP请求,而是模拟HTTP请求内容,从而节省了HTTP请求的网络传输,测试速度更快。

3.1 Mockito基本用法

@Slf4j
public class ArticleControllerTest {

    //mock对象
    private static MockMvc mockMvc;

    //在所有测试方法执行之前进行mock对象初始化
    @BeforeAll
    static void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(new ArticleController()).build();
    }

    //测试方法
    @Test
    public void saveArticle() throws Exception {

        String articleParam = "{\n" +
                "    \"id\": 1,\n" +
                "    \"author\": \"William\",\n" +
                "    \"title\": \"spring boot\",\n" +
                "    \"content\": \"c\",\n" +
                "    \"createTime\": \"2023-06-06 05:23:34\",\n" +
                "    \"reader\":[{\"name\":\"Jerry\",\"age\":18},{\"name\":\"Jack\",\"age\":37}]\n" +
                "}";
        MvcResult result = mockMvc.perform(
            MockMvcRequestBuilders
                .request(HttpMethod.POST, "/rest/articles")
                .contentType("application/json")
                .content(articleParam)
        )
        .andExpect(MockMvcResultMatchers.status().isOk())  //HTTP:status 200
        .andExpect(MockMvcResultMatchers.jsonPath("$.data.author").value("William"))
        .andExpect(MockMvcResultMatchers.jsonPath("$.data.reader[0].age").value(18))
        .andDo(print())
        .andReturn();

        result.getResponse().setCharacterEncoding("UTF-8");
        log.info(result.getResponse().getContentAsString());
    }
}

MockMvc对象有以下几个基本的方法:

  • perform : 模拟执行一个RequestBuilder构建的HTTP请求,会执行SpringMVC的流程并映射到相应的控制器Controller执行。
  • contentType:发送请求内容的序列化的格式,"application/json"表示JSON数据格式
  • andExpect: 添加RequsetMatcher验证规则,验证控制器执行完成后结果是否正确,或者说是结果是否与我们期望(Expect)的一致。
  • andDo: 添加ResultHandler结果处理器,比如调试时打印结果到控制台
  • andReturn: 最后返回相应的MvcResult,然后进行自定义验证/进行下一步的异步处理

上面的整个过程,我们都没有使用到Spring Context依赖注入、也没有启动tomcat web容器。整个测试的过程十分的轻量级,速度很快。

3.2 在真实servlet容器环境下Mock测试

上面的测试执行速度非常快,但是有一个问题:它没有启动servlet容器和Spring 上下文,自然也就无法实现依赖注入(不支持@Resource和@AutoWired注解),这就导致它在全流程测试中有很大的局限性。同时如果ArticleController依赖于ArticleService对象,但是ArticleService代码还没写是一个空类空方法不能用,我们就可以mock一个ArticleService来完成测试。
ArticleService接口如下:

public interface ArticleService {
  public String saveArticle(Article article);
}

ArticleController 如下:

@Slf4j
@RequestMapping("/rest")
@RestController
public class ArticleController {

    @Resource
    private ArticleService articleService;
    
    @PostMapping("/articles")
    public AjaxResponse saveArticle(@RequestBody Article article){
        
        log.info("saveArticle:" + article);
        Article result = articleService.saveArticle(article);
        return AjaxResponse.success(result);
    }
    
}

测试类如下:

@Slf4j
@AutoConfigureMockMvc
@SpringBootTest
@ExtendWith(SpringExtension.class)
public class ArticleControllerTest {

    @Resource
    private MockMvc mockMvc;

    @MockBean
    private ArticleService articleService;

    //测试方法
    @Test
    public void saveArticle() throws Exception {

        String articleParam = "{\n" +
                "    \"id\": 1,\n" +
                "    \"author\": \"William\",\n" +
                "    \"title\": \"spring boot\",\n" +
                "    \"content\": \"c\",\n" +
                "    \"createTime\": \"2023-06-06 05:23:34\",\n" +
                "    \"reader\":[{\"name\":\"Jerry\",\"age\":18},{\"name\":\"Jack\",\"age\":37}]\n" +
                "}";

        ObjectMapper mapper = new ObjectMapper();
        Article article = mapper.readValue(articleParam , Article.class);
        Mockito.when(articleService.saveArticle(article)).thenReturn(article);

        MvcResult result = mockMvc.perform(
            MockMvcRequestBuilders
                .request(HttpMethod.POST, "/rest/articles")
                .contentType("application/json")
                .content(article)
        )
        .andExpect(MockMvcResultMatchers.status().isOk())  //HTTP:status 200
        .andExpect(MockMvcResultMatchers.jsonPath("$.data.author").value("William"))
        .andExpect(MockMvcResultMatchers.jsonPath("$.data.reader[0].age").value(18))
        .andDo(MockMvcResultHandlers.print())
        .andReturn();

        result.getResponse().setCharacterEncoding("UTF-8");
        log.info(result.getResponse().getContentAsString());
    }
}
  • @AutoConfigureMockMvc,该注解表示对MockMvc进行配置,MockMvc对象由spring 依赖注入构建;
  • @SpringBootTest,是用来创建Spring的上下文ApplicationContext,保证测试在上下文环境里运行;@SpringBootTest 注解包含了 @ExtendWith注解,为我们构造了一个的Servlet容器运行运行环境,并在此环境下测试。
  • @MockBean 可以用MockBean伪造模拟一个Service ,上例中Mock了一个articleService对象。
  • Mockito.when(articleService.saveArticle(articleObj)).thenReturn(articleObj);这是一行打桩代码,告诉测试用例程序,当你调用articleService.saveArticle(article)方法的时候,不要去真的调用这个方法,直接返回一个结果(article)就好了。

该测试方法会真实的启动一个tomcat容器、以及Spring 上下文,所以我们可以进行依赖注入(@Resource),但是有一个非常明显的缺点:每次做一个接口测试,都会真实的启动一次servlet容器,Spring上下文加载项目里面定义的所有的Bean,导致执行过程很缓慢。

四、轻量级测试

在ExtendWith的AutoConfigureMockMvc注解的共同作用下,启动了SpringMVC的运行容器,并且把项目中所有的@Bean全部都注入进来。单把所有的bean都注入进来会很臃肿且会拖慢单元测试的效率。如果我只是想测试一下控制层Controller,怎么办?有没有轻量级的解决方案?答案是有的,我们只需要使用@WebMvcTest替换@SpringBootTest注解即可:

  • @SpringBootTest注解告诉SpringBoot去寻找一个主配置类(例如带有@SpringBootApplication的配置类),并使用它来启动Spring应用程序上下文。SpringBootTest加载完整的应用程序并注入所有可能的bean,因此速度会很慢。
  • @WebMvcTest注解主要用于controller层测试,只覆盖应用程序的controller层,@WebMvcTest(ArticleController.class)只加载ArticleController这一个Bean用作测试。所以WebMvcTest要快得多,因为我们只加载了应用程序的一小部分。

相关文章

网友评论

      本文标题:SpringBoot中的单元测试及Mockito框架

      本文链接:https://www.haomeiwen.com/subject/qghaedtx.html