Как я могу использовать @WebMvcTest для контроллера, который использует автосвязываемый ConversionService?

В приложении Spring Boot у меня есть два POJO, Foo и Bar, и BarToFooConverter, который выглядит так:

@Component
public class BarToFooConverter implements Converter<Bar, Foo> {
    @Override
    public Foo convert(Bar bar) {
        return new Foo(bar.getBar());
    }
}

У меня также есть контроллер, который использует преобразователь:

@RestController("test")
public class TestController {
    @Autowired
    private ConversionService conversionService;

    @RequestMapping(method = RequestMethod.PUT)
    @ResponseBody
    public Foo put(@RequestBody Bar bar) {
        return conversionService.convert(bar, Foo.class);
    }
}

Я хотел бы протестировать этот контроллер с помощью @WebMvcTest, что-то вроде:

@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test() throws Exception {
        mockMvc.perform(
                put("/test")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"bar\":\"test\"}"))
                .andExpect(status().isOk());
    }
}

но когда я запускаю это, я обнаруживаю, что мой BarToFooConverter не был зарегистрирован с ConversionService:

Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.example.demo.web.Bar] to type [com.example.demo.web.Foo]
    at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:324)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:206)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:187)
    at com.example.demo.web.TestController.put(TestController.java:15)

Кажется, это имеет смысл, потому что, согласно Javadoc:

Использование этой аннотации отключит полную автоматическую настройку и вместо этого применит только конфигурацию, относящуюся к тестам MVC (например, @Controller, @ControllerAdvice, @JsonComponent Filter, WebMvcConfigurer и HandlerMethodArgumentResolver bean-компоненты, но не @Component, @Service или @Repository bean-компоненты).

Однако справочное руководство немного отличается, говоря, что @WebMvcTest включает Converters:

@WebMvcTest автоматически настраивает инфраструктуру Spring MVC и ограничивает сканируемые bean-компоненты @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer и HandlerMethodArgumentResolver. Обычные bean-компоненты @Component не сканируются при использовании этой аннотации.

Похоже тут справочник неверный - или я свой Converter неправильно прописываю?

Я также пытался издеваться над ConversionService в своем тесте с помощью:

@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ConversionService conversionService;

    @Test
    public void test() throws Exception {
        when(conversionService.convert(any(Bar.class), eq(Foo.class))).thenReturn(new Foo("test"));

        mockMvc.perform(
                put("/test")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"bar\":\"test\"}"))
                .andExpect(status().isOk());
    }
}

но теперь Spring жалуется, что мой макет ConversionService переопределяет значение по умолчанию:

Caused by: java.lang.IllegalStateException: @Bean method WebMvcConfigurationSupport.mvcConversionService called as a bean reference for type [org.springframework.format.support.FormattingConversionService] but overridden by non-compatible bean instance of type [org.springframework.core.convert.ConversionService$$EnhancerByMockitoWithCGLIB$$da4e303a]. Overriding bean of same name declared in: null
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.obtainBeanInstanceFromFactory(ConfigurationClassEnhancer.java:402)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)
    ...

В идеале я хотел бы использовать свой оригинальный подход с настоящим конвертером в моем тесте, а не издеваться над ConversionService, но с @WebMvcTest, чтобы ограничить объем запускаемых компонентов, поэтому я также попытался использовать includeFilter в аннотации @WebMvcTest:

@WebMvcTest(includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.demo.web.Bar*"))

но он по-прежнему не работает с исходным сообщением об ошибке «Не найден конвертер, способный конвертировать ...».

Это похоже на то, что должно быть довольно распространенным требованием - что мне не хватает?


person DaveyDaveDave    schedule 18.01.2018    source источник
comment
У меня нет времени исследовать это более тщательно сейчас, но просто как упоминание для людей, обнаруживших это - похоже, в версии Spring есть разница - я обновил версию Spring Boot несколько недель назад и обнаружил, что тесты начал терпеть неудачу. Я не могу сейчас вспомнить почему, но думаю, что настоящее ConversionService сейчас вводилось, хотя раньше этого не было.   -  person DaveyDaveDave    schedule 19.10.2018


Ответы (1)


Вы можете зарегистрировать конвертер вручную в @Before аннотированном методе. Все, что вам нужно сделать, это внедрить GenericConversionService и вызовите addConverter(new BarToFooConverter()), чтобы сделать преобразователь разрешимым. В этом случае вы можете избавиться от насмешливой части. Ваш тест может выглядеть так:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest
@RunWith(SpringRunner.class)
public class TestControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private GenericConversionService conversionService;

    @Before
    public void setup() {
        conversionService.addConverter(new BarToFooConverter());
    }

    @Test
    public void test() throws Exception {
        mockMvc.perform(
                put("/test")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"bar\":\"test\"}"))
                .andExpect(status().isOk());
    }
}

Альтернативное решение: Spring Boot, начиная с версии 1.4.0, предоставляет коллекция автоконфигураций, связанных с тестами, и одна из этих автоконфигураций - @AutoConfigureMockMvc, который настраивает MockMvc компонент который отлично работает с инжекторными компонентами преобразователя.

person Szymon Stepniak    schedule 18.01.2018