How to cleanly test Spring Controllers that retrieve parameters with DomainClassConverter?

前端 未结 1 1079
夕颜
夕颜 2021-01-22 23:58

I am big on clean well-isolated unit tests. But I am stumbling on the \"clean\" part here for testings a controller that uses DomainClassConverter feature to get en

相关标签:
1条回答
  • 2021-01-23 00:59

    So from the DomainClassConverter small documentation I know that it uses CrudRepository#findById to find entities. What I would like to know is how can I mock that cleanly in a test.

    You will need to mock 2 methods that are called prior the CrudRepository#findById in order to return the entity you want. The example below is using RestAssuredMockMvc, but you can do the same thing with MockMvc if you inject the WebApplicationContext as well.

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = SomeApplication.class)
    public class SomeControllerTest {
    
        @Autowired
        private WebApplicationContext context;
    
        @MockBean(name = "mvcConversionService")
        private WebConversionService webConversionService;
    
        @Before
        public void setup() {
            RestAssuredMockMvc.webAppContextSetup(context);
    
            SomeEntity someEntity = new SomeEntity();
    
            when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
                    .thenReturn(true);
    
            when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
                    .thenReturn(someEntity);
        }
    }
    

    At some point Spring Boot will execute the WebConversionService::convert, which will later call DomainClassConverter::convert and then something like invoker.invokeFindById, which will use the entity repository to find the entity.

    So why mock WebConversionService instead of DomainClassConverter? Because DomainClassConverter is instantiated during application startup without injection:

    DomainClassConverter<FormattingConversionService> converter =
            new DomainClassConverter<>(conversionService);
    

    Meanwhile, WebConversionService is a bean which will allow us to mock it:

    @Bean
    @Override
    public FormattingConversionService mvcConversionService() {
        WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
        addFormatters(conversionService);
        return conversionService;
    }
    

    It is important to name the mock bean as mvcConversionService, otherwise it won't replace the original bean.

    Regarding the stubs, you will need to mock 2 methods. First you must tell that your mock can convert anything:

    when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
            .thenReturn(true);
    

    And then the main method, which will match the desired entity ID defined in the URL path:

    when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
            .thenReturn(someEntity);
    

    So far so good. But wouldn't be better to match the destination type as well? Something like eq(TypeDescriptor.valueOf(SomeEntity.class))? It would, but this creates a new instance of a TypeDescriptor, which will not match when this stub is called during the domain conversion.

    This was the cleanest solution I've put to work, but I know that it could be a lot better if Spring would allow it.

    0 讨论(0)
提交回复
热议问题