首页 > 解决方案 > 通过(伪)直接调用该处理程序方法来测试 Spring 控制器 - 好还是坏?如何实施?

问题描述

一句话:我要MockMvc直接调用控制器一样执行。

(PS 那是语法糖。这并不意味着我在集成测试时真的在调用控制器。)


细节:

假设我们有一个 Restful 控制器:

class BookController {
    public Book updateBook(int id, Book newBook) {...}
}

RESTful 服务的典型Spring 集成测试如下所示:

mockMvc.perform(put("/books/1")
                .content("{\"id\":1, \"name\": \"ABC\", ...}")
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("ABC")))
                ...and more...;

但是,我们可以这样做:

BookController magicBookController = SomeMagic.generate_the_magic_controller();
Book result = magicMvc.perform(magicBookController.updateBook(1, new Book("ABC", ...)));
assertThat(1, result.getId());
assertThat("ABC", result.getName());
...

编辑:上面的代码不仅仅是调用new BookController().updateBook(...)方法!我希望的是:generate_the_magic_controller将生成动态代理(使用 cglib)。然后,当我们调用 时magicBookController.updateBook,实际上动态生成的代码如下所示:

Book dynamically_generated_updateBook(int id, Book book) {
    String url = magic_assemble_url(id); // will become: "/books/1"
    String content = magic_assemble_content(book); // will become: "{name: AAA, ...}"
    Something result = mockMvc.perform(put(url)
                .content(content)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON));
    return parse_result(result); //parse back into book
}

一句话:我要MockMvc直接调用控制器一样执行。我的问题:

  1. 我要不要做?(或者这是一个非常糟糕的做法?)
  2. 怎么做?我正在考虑破解 Spring 框架中关于“查找和解析控制器”的部分,但没有关于如何做到这一点的具体想法......

编辑:我的测试目标如下。最初,人们喜欢用眼睛测试代码(给出输入并查看输出并通过他们的头脑进行断言)。当然,这很糟糕。所以我们写下诸如“发布到 /books 并断言结果是正确的”之类的内容。这就是我想要测试的。它实际上有点像 E2E 测试(因为这是一项宁静的服务)恕我直言。(或者我的目标完全错误?)


编辑:泽西岛的一个典型测试,我认为(个人)比 Spring 测试更优雅:

Profile profile = resources.getJerseyTest()
                              .target("/v1/profile/" + AuthHelper.VALID_NUMBER_TWO)
                              .request()
                              .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
                              .get(Profile.class);
assertEquals(profile.getXXX(), "aaa");
...

非常感谢您的任何想法!

标签: javaspringspring-bootspring-mvctesting

解决方案


重点MockMvc是使用 spring-mvc 基础设施测试 REST API 是否正确配置,例如可以将 HTTP 请求映射到并使用正确的参数调用预期的控制器方法,从控制器方法返回的 java 对象可以是在 HTTP 响应等中序列化到正确的 JSON 正文。它是关于测试 REST API 是否在给定 HTTP 请求的情况下正确运行,所以如果你想使用,你必须指定一个 HTTP 请求MockMvc

在我看来,您想测试您的 REST 客户端,例如它可以将请求发送到具有预期正文的预期 URL,并且它可以将 JSON 响应反序列化回 java 对象等,而不是测试您的 REST API 配置正确。Wiremock如果是,您可以使用或之类的工具来存根 REST API okhttp MockWebServer,并像往常一样使用 REST 客户端来调用存根 API。

所以,首先要问自己的是,你实际上想测试什么?通常我们每次只专注于测试一件事。最好直接以声明方式指定测试输入,而不涉及任何复杂的代码转换,例如将一些给定的输入转换为测试真正需要的输入,因为更多的转换代码意味着更多的机会引入错误的另一个方面。

因此,如果您想测试是否正确配置了 spring-mvc API(即使用 mockMvc),那么实现将生成的 rest 客户端转换为调用 API 实际需要的 HTTP 请求的东西是一个坏主意。


推荐阅读