代码之家  ›  专栏  ›  技术社区  ›  ErosC

为什么webflux的spring boot测试会忽略自定义jackson模块

  •  3
  • ErosC  · 技术社区  · 6 年前

    我正在编写一个使用Spring Boot 2.0.1和WebFlux路由器功能的应用程序( 基于注释!)。对于我的一些数据对象,我编写了自定义序列化程序,可以扩展 StdSerializer .我在 SimpleModule 并将该模块作为bean公开。

    当我运行应用程序时,这个设置非常有效。bean被实例化,其余响应被使用正确的序列化器序列化。

    现在,我想编写一个测试,验证路由器功能及其背后的处理程序是否按预期工作。我想要模拟的处理程序背后的服务。然而,在测试中,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公开。此豆子被正确拾取并添加到 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实例,但也没有使用。我想知道我是否错过了一个设置(虽然我尝试了很多注释…)或者如果我碰到了虫子。我在谷歌上做了很多搜索,但迄今为止,我找到的任何解决方案都没有帮助。此外,目前很少有ppl使用路由器功能:)。

    感谢您的帮助!

    更新时间: 我尝试了2.0.2。版本和2.1.0。以及BUILD-20180509。结果总是一样的。

    2 回复  |  直到 6 年前
        1
  •  5
  •   Sébastien Deleuze    6 年前

    而不是创建 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\"}");
        }
    }
    
        2
  •  2
  •   ErosC    6 年前

    虽然Sbastien提供的解决方案在演示代码中工作得很完美,但在将其引入主应用程序后,我遇到了一些问题@SpringBootTest会引入太多的bean,这反过来又需要大量的外部配置设置等,测试应该只覆盖路由和序列化。

    然而,删除@SpringBootTest将使我无法再次进行自定义序列化。所以我玩了一下@自动配置。。。注释并找到了一个集,该集允许我在模拟/省略其他所有内容的同时测试路由和序列化。

    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
    }