1. 概述

在测试我们的REST端点时,有时我们需要获取响应并将其转换为对象以便进一步检查和验证。众所周知,一种方法是使用像RestAssured这样的库来验证响应,而无需将其转换为对象。

在这个教程中,我们将探讨使用MockMVCSpring Boot以几种方式获取JSON内容并作为对象进行操作的方法。

2. 示例设置

在深入之前,让我们创建一个简单的用于测试的REST端点。

首先,设置依赖。我们需要在pom.xml中添加spring-boot-starter-web依赖,以便我们可以创建REST端点:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

接下来,定义Article类:

public class Article {
    private Long id;
    private String title;
    
    // standard getters and setters
}

然后,创建包含两个端点的ArticleController,一个返回单篇文章,另一个返回文章列表:

@RestController
@RequestMapping
public class ArticleController {
    @GetMapping("/article")
    public Article getArticle() {
        return new Article(1L, "Learn Spring Boot");
    }

    @GetMapping("/articles")
    public List<Article> getArticles() {
        return List.of(new Article(1L, "Guide to JUnit"), new Article(2L, "Working with Hibernate"));
    }
}

3. 测试类

为了测试控制器,我们将测试类标记为@WebMvcTest注解。当我们使用这个注解时,Spring Boot会自动配置MockMvc并只为web层启动上下文

此外,我们可以指定只实例化ArticleController控制器的上下文,这对于具有多个控制器的应用程序很有用:

@WebMvcTest(ArticleController.class)
class ArticleControllerUnitTest {
    @Autowired
    private MockMvc mockMvc;
}

我们也可以使用@AutoConfigureMockMvc注解来配置MockMvc,但这需要Spring Boot运行整个应用程序上下文,这会使我们的测试运行变慢。

现在,我们的准备工作就绪了,让我们探索如何使用MockMvc执行请求并获取对象形式的响应。

4. 使用Jackson

将JSON内容转换为对象的一种方法是使用Jackson库。

4.1. 获取单个对象

让我们创建一个测试,验证HTTP GET /article 端点是否按预期工作。

由于我们希望将响应转换为对象,首先调用mockMvcandReturn()方法来获取结果:

MvcResult result = this.mockMvc.perform(get("/article"))
  .andExpect(status().isOk())
  .andReturn();

andReturn()方法返回MvcResult对象,它允许我们执行工具不支持的额外验证。

此外,我们可以调用getContentAsString()方法来获取响应作为String。不幸的是,MockMvc没有定义我们可以用来将响应转换为特定对象类型的明确方法。我们需要自己指定逻辑。

我们将使用Jackson的ObjectMapper将JSON内容转换为我们想要的类型。

调用readValue()方法,并传入字符串形式的响应以及我们想要转换的目标类型:

String json = result.getResponse().getContentAsString();
Article article = objectMapper.readValue(json, Article.class);

assertNotNull(article);
assertEquals(1L, article.getId());
assertEquals("Learn Spring Boot", article.getTitle());

4.2. 获取对象集合

现在来看看当端点返回集合时如何获取响应。

在上一节中,当我们想要获取单个对象时,我们指定了类型为Article.class。但是,对于泛型类型(如集合)来说,这是不可能的。我们不能指定类型为List<Article>.class

我们可以使用Jackson将集合反序列化的一种方法是使用TypeReference泛型类:

@Test
void whenGetArticle_thenReturnListUsingJacksonTypeReference() throws Exception {
    MvcResult result = this.mockMvc.perform(get("/articles"))
      .andExpect(status().isOk())
      .andReturn();

    String json = result.getResponse().getContentAsString();
    List<Article> articles = objectMapper.readValue(json, new TypeReference<>(){});

    assertNotNull(articles);
    assertEquals(2, articles.size());
}

由于类型擦除,运行时无法获得泛型类型信息。为了克服这个限制,TypeReference在编译时捕获了我们想要将JSON转换为的类型。

此外,我们可以通过指定CollectionType实现相同的功能:

String json = result.getResponse().getContentAsString();
CollectionType collectionType = objectMapper.getTypeFactory().constructCollectionType(List.class, Article.class);
List<Article> articles = objectMapper.readValue(json, collectionType);

assertNotNull(articles);
assertEquals(2, articles.size());

5. 使用Gson

现在,让我们看看如何使用Gson库将JSON内容转换为对象。

首先,在pom.xml中添加所需的依赖

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

5.1. 获取单个对象

我们可以调用Gson实例的fromJson()方法,将内容和期望类型传递给它,从而将JSON转换为对象:

@Test
void whenGetArticle_thenReturnArticleObjectUsingGson() throws Exception {
    MvcResult result = this.mockMvc.perform(get("/article"))
      .andExpect(status().isOk())
      .andReturn();

    String json = result.getResponse().getContentAsString();
    Article article = new Gson().fromJson(json, Article.class);

    assertNotNull(article);
    assertEquals(1L, article.getId());
    assertEquals("Learn Spring Boot", article.getTitle());
}

5.2. 获取对象集合

最后,让我们看看如何使用Gson处理集合。

要使用Gson反序列化集合,我们可以指定TypeToken

@Test
void whenGetArticle_thenReturnArticleListUsingGson() throws Exception {
    MvcResult result = this.mockMvc.perform(get("/articles"))
      .andExpect(status().isOk())
      .andReturn();

    String json = result.getResponse().getContentAsString();
    TypeToken<List<Article>> typeToken = new TypeToken<>(){};
    List<Article> articles = new Gson().fromJson(json, typeToken.getType());

    assertNotNull(articles);
    assertEquals(2, articles.size());
}

这里,我们为文章元素的列表定义了TypeToken。然后,在fromJson()方法中,我们调用getType()返回Type对象。Gson使用反射(Java反射)来确定我们想要将JSON转换为什么类型的对象。

6. 总结

在这篇文章中,我们学习了在使用MockMVC工具时获取JSON内容作为对象的几种方法。

总之,我们可以使用Jackson的ObjectMapperString响应转换为目标类型。在处理集合时,我们需要指定TypeReferenceCollectionType。同样,我们也可以使用Gson库来反序列化对象。

如往常一样,完整的源代码可以在GitHub上找到。