Issue
I'm trying to code a unit test for a method defined in a controller. The method is like this:
@RestController
@RequestMapping("/products")
public class RestProductController {
@RequestMapping(value="/{product}/skus", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public List<SkuSummaryVO> getSkuByProduct(@Valid @PathVariable Product product){
List<SkuSummaryVO> skusByProductVOs = skuService.getSkusByProduct(product);
return skusByProductVOs;
}
}
We use in our Configuration class the annotation @EnableSpringDataWebSupport to enable the DomainClassConverter feature. So we can use the JPA entity as a @PathVariable. So when a product id will be set in the URL, we will get the product (with a request behind the scene).
We are developing unit test without enabling the Spring App Context and using Mockito. So we initialize the mockMvcBuilders like this:
public class RestProductControllerTest {
...
@Before
public void setUp() {
RestProductController restProductController = new RestProductController();
...
mockMvc = MockMvcBuilders.standaloneSetup(restProductController).build();
}
}
and the test method is like this:
@Test
public void testGetProductById() throws Exception {
...
String jsonResult = ...;
mockMvc.perform(get("/products/123/skus").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(content().string(jsonResult));
}
And I get a 500 for the HttpCode (the status)
And the unit tests work fine for the controller methods witch are not using the DomainClassConverter feature (for example if I use a Long productId
instead of a Product product
as a parameter of getSkuByProduct
, it will work)
Solution
UPDATE: on second thought, what I originally proposed below could never work since DomainClassConverter
requires that your Spring Data Repositories be present in the ApplicationContext
, and in your example you are using StandaloneMockMvcBuilder
which will never create an ApplicationContext
that contains your Spring Data Repositories.
The way I see it, you have two options.
- Convert your test to an integration test using a real
ApplicationContext
(loaded via@ContextConfiguration
as demonstrated in the reference manual) and pass that toMockMvcBuilders.webAppContextSetup(WebApplicationContext)
. If the configuredApplicationContext
includes your Spring Data web configuration, you should be good to go. - Forgo the use of
DomainClassConverter
in your unit test, and instead set a customHandlerMethodArgumentResolver
(e.g., a stub or a mock) viaStandaloneMockMvcBuilder.setCustomArgumentResolvers(HandlerMethodArgumentResolver...)
. Your custom resolver could then return whateverProduct
instance you desire.
You'll have to register an instance of the DomainClassConverter
with the ConversionService
in the StandaloneMockMvcBuilder
that is created when you invoke MockMvcBuilders.standaloneSetup(Object...)
.
In SpringDataWebConfiguration.registerDomainClassConverterFor()
, you can see that the DomainClassConverter
is instantiated (and indirectly registered) like this:
DomainClassConverter<FormattingConversionService> converter =
new DomainClassConverter<FormattingConversionService>(conversionService);
converter.setApplicationContext(context);
And you can set your own FormattingConversionService
via StandaloneMockMvcBuilder.setConversionService()
. See WebMvcConfigurationSupport.mvcConversionService()
for an example of how to configure the ConversionService
for a web environment.
The challenge then is how to obtain a reference to the ApplicationContext
. Internally, StandaloneMockMvcBuilder
uses a StubWebApplicationContext
, but as far as I can see (prior to Spring 4.1) there is no way to access it directly without subclassing StandaloneMockMvcBuilder
.
As of Spring Framework 4.1, you could implement a custom MockMvcConfigurer
(which gives you access to the WebApplicationContext
via its beforeMockMvcCreated()
method.
So hopefully that's enough information to get you on the right track!
Good luck...
Sam
Answered By - Sam Brannen