为什么 spring 使用 webflux 的启动测试会忽略自定义 jackson 模块
Why does spring boot test with webflux ignore custom jackson module
我正在使用 Spring Boot 2.0.1 和 WebFlux 路由器功能(不是 基于注释的!)编写一个应用程序。对于我的一些数据对象,我编写了扩展 StdSerializer
的自定义序列化程序。这些我在 SimpleModule
中注册并将该模块公开为 bean。
当我 运行 应用程序时,此设置非常有效。该 bean 已实例化,REST 响应已使用正确的序列化程序序列化。
现在我想编写一个测试来验证路由器功能及其背后的处理程序是否按预期工作。我想模拟的处理程序背后的服务。但是,在测试中,REST 响应使用 默认序列化程序 .
我创建了一个重现该问题的小型演示项目。完整代码可以在这里找到:http://s000.tinyupload.com/?file_id=82815835861287011625
Gradle 配置加载 Spring 引导和一些依赖项以支持 WebFlux 和测试。
import io.spring.gradle.dependencymanagement.DependencyManagementPlugin
import org.springframework.boot.gradle.plugin.SpringBootPlugin
buildscript {
ext {
springBootVersion = '2.0.1.RELEASE'
}
repositories {
mavenCentral()
// To allow to pull in milestone releases from Spring
maven { url 'https://repo.spring.io/milestone' }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("io.spring.gradle:dependency-management-plugin:1.0.5.RELEASE")
}
}
apply plugin: 'java'
apply plugin: SpringBootPlugin
apply plugin: DependencyManagementPlugin
repositories {
mavenCentral()
// To allow to pull in milestone releases from Spring
maven { url 'https://repo.spring.io/milestone' }
}
dependencyManagement {
imports {
mavenBom 'org.springframework.boot:spring-boot-dependencies:2.0.1.RELEASE'
}
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'org.slf4s:slf4s-api_2.12:1.7.25'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'org.springframework.boot:spring-boot-starter-json'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:2.+"
}
数据对象有两个字段。
package com.example.model;
public class ReverserResult {
private String originalString;
private String reversedString;
// ... constructor, getters
}
自定义序列化程序以与默认序列化程序完全不同的方式呈现数据对象。原始字段名称消失,数据对象的内容被压缩成一个字符串。
@Component
public class ReverserResultSerializer extends StdSerializer<ReverserResult> {
// ... Constructor ...
@Override
public void serialize(ReverserResult value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeFieldName("result");
gen.writeString(value.getOriginalString() + "|" + value.getReversedString());
gen.writeEndObject();
}
}
序列化程序包装在 Jackson 模块中并作为 bean 公开。当 运行 实际应用时,这个 bean 被正确地拾取并添加到 ObjectMapper
。
@Configuration
public class SerializerConfig {
@Bean
@Autowired public Module specificSerializers(ReverserResultSerializer reverserResultSerializer) {
SimpleModule serializerModule = new SimpleModule();
serializerModule.addSerializer(ReverserResult.class, reverserResultSerializer);
return serializerModule;
}
}
我还验证了该 bean 确实存在于测试中。所以我可以排除在测试期间创建的上下文缺少加载 bean。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReverserRouteTest {
@Autowired
public ReverserRoutes reverserRoutes;
@MockBean
public ReverserService mockReverserService;
@Autowired
@Qualifier("specificSerializers")
public Module jacksonModule;
@Test
public void testSerializerBeanIsPresent() {
assertNotNull(jacksonModule);
}
@Test
public void testRouteAcceptsCall() {
given(mockReverserService.reverse(anyString())).willReturn(new ReverserResult("foo", "bar"));
WebTestClient client = WebTestClient.bindToRouterFunction(reverserRoutes.createRouterFunction()).build();
client.get().uri("/reverse/FooBar").exchange().expectStatus().isOk();
}
@Test
public void testRouteReturnsMockedResult() {
given(mockReverserService.reverse(anyString())).willReturn(new ReverserResult("foo", "bar"));
WebTestClient client = WebTestClient.bindToRouterFunction(reverserRoutes.createRouterFunction()).build();
client.get().uri("/reverse/somethingcompletelydifferent")
.exchange()
.expectBody().json("{\"result\":\"foo|bar\"}");
}
}
运行应用程序时的结果:
GET http://localhost:9090/reverse/FooBar
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json;charset=UTF-8
{
"result": "FooBar|raBooF"
}
运行测试时的结果:
< 200 OK
< Content-Type: [application/json;charset=UTF-8]
{"originalString":"foo","reversedString":"bar"}
我也尝试创建自己的 ObjectMapper 实例,但也没有使用。我想知道我是否缺少设置(虽然我确实尝试了很多注释......)或者我是否遇到了错误。我在 Google 和 SO 上进行了大量搜索,但到目前为止,我发现的解决方案中 none 有帮助。此外,到目前为止,很少有人在使用路由器功能 :)。
感谢任何帮助!
更新: 我也尝试使用 2.0.2.RELEASE 和 2.1.0.BUILD-20180509。结果总是一样的。
无需在测试中手动创建 WebTestClient
,您可以利用 @AutoConfigureWebTestClient
并按以下方式自动装配它,以便正确考虑您的 Jackson 模块:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWebTestClient
public class ReverserRouteTest {
@MockBean
public ReverserService mockReverserService;
@Autowired
@Qualifier("specificSerializers")
public Module jacksonModule;
@Autowired
public WebTestClient client;
@Test
public void testSerializerBeanIsPresent() {
assertNotNull(jacksonModule);
}
@Test
public void testRouteAcceptsCall() {
given(mockReverserService.reverse(anyString())).willReturn(new ReverserResult("foo", "bar"));
client.get().uri("/reverse/FooBar").exchange().expectStatus().isOk();
}
@Test
public void testRouteReturnsMockedResult() {
given(mockReverserService.reverse(anyString())).willReturn(new ReverserResult("foo", "bar"));
client.get().uri("/reverse/somethingcompletelydifferent")
.exchange()
.expectBody().json("{\"result\":\"foo|bar\"}");
}
}
虽然 Sébastien 提供的解决方案在演示代码中完美运行,但在将其引入主应用程序后我遇到了一些问题。 @SpringBootTest 会引入太多的bean,这反过来又需要大量的外部配置设置等。而且测试应该只覆盖路由和序列化。
然而,删除@SpringBootTest 将使我无法再进行自定义序列化。所以我玩了一下 @AutoConfigure... 注释并找到了一个允许我测试路由和序列化的集合,同时 mocking/omitting 其他一切。
完整代码可在 GitHub https://github.com/DerEros/demo-webflux-test-issue/tree/with-webfluxtest.
上找到
相关更改在这里。希望这对其他人也有帮助。
@RunWith(SpringRunner.class)
@WebFluxTest
@AutoConfigureWebClient
@Import({SerializerConfig.class, ReverserResultSerializer.class, ReverserRoutes.class, ReverseHandler.class, ReverserConfig.class})
public class ReverserRouteTest {
@MockBean
public ReverserService mockReverserService;
@Autowired
@Qualifier("specificSerializers")
public Module jacksonModule;
@Autowired
public WebTestClient client;
// Tests; no changes here
}
我正在使用 Spring Boot 2.0.1 和 WebFlux 路由器功能(不是 基于注释的!)编写一个应用程序。对于我的一些数据对象,我编写了扩展 StdSerializer
的自定义序列化程序。这些我在 SimpleModule
中注册并将该模块公开为 bean。
当我 运行 应用程序时,此设置非常有效。该 bean 已实例化,REST 响应已使用正确的序列化程序序列化。
现在我想编写一个测试来验证路由器功能及其背后的处理程序是否按预期工作。我想模拟的处理程序背后的服务。但是,在测试中,REST 响应使用 默认序列化程序 .
我创建了一个重现该问题的小型演示项目。完整代码可以在这里找到:http://s000.tinyupload.com/?file_id=82815835861287011625
Gradle 配置加载 Spring 引导和一些依赖项以支持 WebFlux 和测试。
import io.spring.gradle.dependencymanagement.DependencyManagementPlugin
import org.springframework.boot.gradle.plugin.SpringBootPlugin
buildscript {
ext {
springBootVersion = '2.0.1.RELEASE'
}
repositories {
mavenCentral()
// To allow to pull in milestone releases from Spring
maven { url 'https://repo.spring.io/milestone' }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("io.spring.gradle:dependency-management-plugin:1.0.5.RELEASE")
}
}
apply plugin: 'java'
apply plugin: SpringBootPlugin
apply plugin: DependencyManagementPlugin
repositories {
mavenCentral()
// To allow to pull in milestone releases from Spring
maven { url 'https://repo.spring.io/milestone' }
}
dependencyManagement {
imports {
mavenBom 'org.springframework.boot:spring-boot-dependencies:2.0.1.RELEASE'
}
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'org.slf4s:slf4s-api_2.12:1.7.25'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'org.springframework.boot:spring-boot-starter-json'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:2.+"
}
数据对象有两个字段。
package com.example.model;
public class ReverserResult {
private String originalString;
private String reversedString;
// ... constructor, getters
}
自定义序列化程序以与默认序列化程序完全不同的方式呈现数据对象。原始字段名称消失,数据对象的内容被压缩成一个字符串。
@Component
public class ReverserResultSerializer extends StdSerializer<ReverserResult> {
// ... Constructor ...
@Override
public void serialize(ReverserResult value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeFieldName("result");
gen.writeString(value.getOriginalString() + "|" + value.getReversedString());
gen.writeEndObject();
}
}
序列化程序包装在 Jackson 模块中并作为 bean 公开。当 运行 实际应用时,这个 bean 被正确地拾取并添加到 ObjectMapper
。
@Configuration
public class SerializerConfig {
@Bean
@Autowired public Module specificSerializers(ReverserResultSerializer reverserResultSerializer) {
SimpleModule serializerModule = new SimpleModule();
serializerModule.addSerializer(ReverserResult.class, reverserResultSerializer);
return serializerModule;
}
}
我还验证了该 bean 确实存在于测试中。所以我可以排除在测试期间创建的上下文缺少加载 bean。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReverserRouteTest {
@Autowired
public ReverserRoutes reverserRoutes;
@MockBean
public ReverserService mockReverserService;
@Autowired
@Qualifier("specificSerializers")
public Module jacksonModule;
@Test
public void testSerializerBeanIsPresent() {
assertNotNull(jacksonModule);
}
@Test
public void testRouteAcceptsCall() {
given(mockReverserService.reverse(anyString())).willReturn(new ReverserResult("foo", "bar"));
WebTestClient client = WebTestClient.bindToRouterFunction(reverserRoutes.createRouterFunction()).build();
client.get().uri("/reverse/FooBar").exchange().expectStatus().isOk();
}
@Test
public void testRouteReturnsMockedResult() {
given(mockReverserService.reverse(anyString())).willReturn(new ReverserResult("foo", "bar"));
WebTestClient client = WebTestClient.bindToRouterFunction(reverserRoutes.createRouterFunction()).build();
client.get().uri("/reverse/somethingcompletelydifferent")
.exchange()
.expectBody().json("{\"result\":\"foo|bar\"}");
}
}
运行应用程序时的结果:
GET http://localhost:9090/reverse/FooBar
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json;charset=UTF-8
{
"result": "FooBar|raBooF"
}
运行测试时的结果:
< 200 OK
< Content-Type: [application/json;charset=UTF-8]
{"originalString":"foo","reversedString":"bar"}
我也尝试创建自己的 ObjectMapper 实例,但也没有使用。我想知道我是否缺少设置(虽然我确实尝试了很多注释......)或者我是否遇到了错误。我在 Google 和 SO 上进行了大量搜索,但到目前为止,我发现的解决方案中 none 有帮助。此外,到目前为止,很少有人在使用路由器功能 :)。
感谢任何帮助!
更新: 我也尝试使用 2.0.2.RELEASE 和 2.1.0.BUILD-20180509。结果总是一样的。
无需在测试中手动创建 WebTestClient
,您可以利用 @AutoConfigureWebTestClient
并按以下方式自动装配它,以便正确考虑您的 Jackson 模块:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWebTestClient
public class ReverserRouteTest {
@MockBean
public ReverserService mockReverserService;
@Autowired
@Qualifier("specificSerializers")
public Module jacksonModule;
@Autowired
public WebTestClient client;
@Test
public void testSerializerBeanIsPresent() {
assertNotNull(jacksonModule);
}
@Test
public void testRouteAcceptsCall() {
given(mockReverserService.reverse(anyString())).willReturn(new ReverserResult("foo", "bar"));
client.get().uri("/reverse/FooBar").exchange().expectStatus().isOk();
}
@Test
public void testRouteReturnsMockedResult() {
given(mockReverserService.reverse(anyString())).willReturn(new ReverserResult("foo", "bar"));
client.get().uri("/reverse/somethingcompletelydifferent")
.exchange()
.expectBody().json("{\"result\":\"foo|bar\"}");
}
}
虽然 Sébastien 提供的解决方案在演示代码中完美运行,但在将其引入主应用程序后我遇到了一些问题。 @SpringBootTest 会引入太多的bean,这反过来又需要大量的外部配置设置等。而且测试应该只覆盖路由和序列化。
然而,删除@SpringBootTest 将使我无法再进行自定义序列化。所以我玩了一下 @AutoConfigure... 注释并找到了一个允许我测试路由和序列化的集合,同时 mocking/omitting 其他一切。
完整代码可在 GitHub https://github.com/DerEros/demo-webflux-test-issue/tree/with-webfluxtest.
上找到相关更改在这里。希望这对其他人也有帮助。
@RunWith(SpringRunner.class)
@WebFluxTest
@AutoConfigureWebClient
@Import({SerializerConfig.class, ReverserResultSerializer.class, ReverserRoutes.class, ReverseHandler.class, ReverserConfig.class})
public class ReverserRouteTest {
@MockBean
public ReverserService mockReverserService;
@Autowired
@Qualifier("specificSerializers")
public Module jacksonModule;
@Autowired
public WebTestClient client;
// Tests; no changes here
}