Spring context compile time validation - java

Is there any tool or maven plugin that can validate Spring context during compile time or maven build execution?
I understand that it's not trivial to check the full correctness of the context without the app start, but it will be good to check some trivial cases, for example if you define a bean in xml context then the bean class have to be present in classpath.

Every Spring Guide contains such sanity test.
For Spring MVC should test with MockMvc test. To verify that Spring configuration is OK, you can Create full context and fire requests against URL and cover also validation + all the Spring wiring. Such test is executed during test maven phase.
Something like this:
#WebAppConfiguration
#ContextConfiguration(classes = RestApplication.class)
public class RestApplicationContextTest extends
AbstractTestNGSpringContextTests {
private static final String FULL_USER_URL = "http://localhost:10403/users";
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
private static String createTestRecord(int identifier) {
String testingRecordString =
"{\"email\": \"user%d#gmail.com\", \"name\": \"User%d\"}";
return String.format(testingRecordString, identifier, identifier,
identifier);
}
#Test
public void testPost() throws Exception {
// GIVEN
String testingRecord = createTestRecord(0);
// WHEN
// #formatter:off
MvcResult mvcResult = mockMvc.perform(post(FULL_USER_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(testingRecord))
.andReturn();
// #formatter:on
// THEN
int httpStatus = mvcResult.getResponse().getStatus();
assertEquals(httpStatus, HttpStatus.CREATED.value());
}
...

Related

Junit MockMvc perform POST with path variable in URL return 404 not found

I have a SpringBoot application with this method in the controller to create an user in the database. The controller is working fine in Postman.
#RestController
#RequestMapping("/v1")
public class UserController {
#PostMapping(value = "/user/{id}")
public void createUser(#PathVariable Integer id, #Valid #RequestBody User request,
BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
throw new RequestValidationException(VALIDATION_ERRORS, bindingResult.getFieldErrors());
}
userService.createUser(id, request), HttpStatus.CREATED);
}
Now I have a junit test case to test this method and I am getting a 404
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
public class UserTest {
private MockMvc mockMvc;
final String CREATE_USER_URL = "/v1/user/" + "10";
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Test
public void testCreateUser() throws Exception {
mockMvc.perform(post(CREATE_USER_URL)
// doesn't work either if I put "/v1/user/10" or post("/v1/user/{id}", 10) here
.content(TestUtils.toJson(request, false))
.contentType(contentType))
.andDo(print())
.andExpect(status().isCreated())
.andReturn();
}
But in the log, I was able to see the correct url:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /v1/user/10
Parameters = {}
Can someone please let me know why I am getting a 404 NOT Found? Thanks.
From docs you need #AutoConfigureMockMvc on class and #Autowire MockMvc
Another useful approach is to not start the server at all, but test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost the full stack is used, and your code will be called exactly the same way as if it was processing a real HTTP request, but without the cost of starting the server. To do that we will use Spring’s MockMvc, and we can ask for that to be injected for us by using the #AutoConfigureMockMvc annotation on the test case:
Code :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class UserTest {
#Autowire
private MockMvc mockMvc;
final String CREATE_USER_URL = "/v1/user/" + "10";
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Test
public void testCreateUser() throws Exception {
mockMvc.perform(post(CREATE_USER_URL)
// doesn't work either if I put "/v1/user/10" or post("/v1/user/{id}", 10) here
.content(TestUtils.toJson(request, false))
.contentType(contentType))
.andDo(print())
.andExpect(status().isCreated())
.andReturn();
}
}
If want to Test your real springboot url Test (End to end Test)
u can use rest-assured or resttemplte
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class)
#TestPropertySource(value={"classpath:application.properties"})
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class SpringRestControllerTest {
#Value("${server.port}")
int port;
#Test
public void getDataTest() {
get("/api/tdd/responseData").then().assertThat().body("data", equalTo("responseData"));
}
#Before
public void setBaseUri () {
RestAssured.port = port;
RestAssured.baseURI = "http://localhost"; // replace as appropriate
}
}
https://dzone.com/articles/test-driven-development-with-spring-boot-rest-api

Mock a REST request of a child service

I try to test a #RestController within a integration test suite using MockMvc.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class WebControllerIT {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
The #RestController (WebController) calls an injected #Service (RestClientService) which uses RestTemplate to call another REST server. This leads to the following error when running the test.
org.springframework.web.client.ResourceAccessException: I/O error on
GET request for "http://test123.com/42/status": test123.com; nested
exception is java.net.UnknownHostException: test123.com
I used MockRestServiceServer for the integration test of the #Service itself but have no idea how to archieve this within the test of #RestController.
How can I simulate a correct REST call of the RestTemplate?
The #RestController class.
#RestController
public class WebController {
private final RestClientService service;
#Autowired
public WebController(RestClientService service) {this.service = service;}
#GetMapping("/status")
public String getStatus() {
// extract pid from database ...
int pid = 42;
return this.service.getStatus(42);
}
}
The #Serviceclass.
#Service
public class RestClientService {
private final RestTemplate restTemplate;
public RestClientService(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
}
Integration/Unit testing doesn't work that way.Objective of this kind of testing is to run through your code and make sure all the business requirement are met but not to hit other system or DB.Here in your case u shouldn't be hitting test123.com to get back data.What needs to done here is that you should mock that method.
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
So that control doesn't enter this method but return you back the mock data(Dummy data).
For example let say that there are 2 kind of status this method is returning and you need to do some business validation based on the string returned.In this case u need to write 2 integration test and make sure the mocking method returns 2 different value(Dummy value instead of hitting that end point)
Reason why we are writing unit testing/integration testing is to make sure your entire code is working as expected but not to hit other system from ur code.
If you want to only test your controller layer, you would do like this.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MockServletContext.class)
#WebAppConfiguration
public class WebControllerIT {
private MockMvc mockMvc;
private RestClientService service
#Mock
private RestTemplate restTemplate
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
service = new RestClientService(restTemplate);
WebController webController = new WebController(service);
mvc = MockMvcBuilders.standaloneSetup(webController).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
//Mock the behaviour of restTemplate.
doReturn("someString").when(restTemplate).getForObject(anyString(), anyString());
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}

Unit Test of file upload using MockMvcBuilders with standalone context and SpringBoot 1.2.5

I'm using Spring Boot 1.2.5-RELEASE. I have a controller that receive a MultipartFile and a String
#RestController
#RequestMapping("file-upload")
public class MyRESTController {
#Autowired
private AService aService;
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void fileUpload(
#RequestParam(value = "file", required = true) final MultipartFile file,
#RequestParam(value = "something", required = true) final String something) {
aService.doSomethingOnDBWith(file, value);
}
}
Now, the service works well. I tested it with PostMan and eveything goes as expected.
Unfortunately, I cannot write a standalone unit test for that code. The current unit test is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
public class ControllerTest{
MockMvc mockMvc;
#Mock
AService aService;
#InjectMocks
MyRESTController controller;
#Before public void setUp(){
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
//File is correctly loaded
final MockMultipartFile multipartFile = new MockMultipartFile("aMultiPartFile.txt", new FileInputStream(file));
doNothing().when(aService).doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
mockMvc.perform(
post("/file-upload")
.requestAttr("file", multipartFile.getBytes())
.requestAttr("something", ":(")
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isCreated());
}
}
Test fails with
java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?
Now, in the MultipartAutoConfiguration class from Spring Boot I see that a MultipartResolver is auto configured. But, I guess that with the standaloneSetup of MockMvcBuilders I cannot access this.
I tried several configurations of the unit test that I don't report for brevity. Especially, I also tried rest-assured as shown here, but honestly this doesn't work because it seems that I cannot mock the AService instance.
Any solution?
You are trying to combine here unit test (standaloneSetup(controller).build();) with Spring integration test (#RunWith(SpringJUnit4ClassRunner.class)).
Do one or the other.
Integration test will need to use something like code below. The problem would be faking of beans. There are ways to fake such bean with #Primary annotation and #Profile annotation (you create testing bean which will override main production bean). I have some examples of such faking of Spring beans (e.g. this bean is replaced by this bean in this test).
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Secodn option is to remove #RunWith(SpringJUnit4ClassRunner.class) and other class level configuration on your test and test controller without Spring Context with standalone setup. That way you can't test validation annotations on your controller, but you can use Spring MVC annotations. Advantage is possibility to fake beans via Mockito (e.g. via InjectMocks and Mock annotations)
I mixed what lkrnak suggested and Mockito #Spy functionality. I use REST-Assured to do the call. So, I did as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
#IntegrationTest({"server.port:0"})
public class ControllerTest{
{
System.setProperty("spring.profiles.active", "unit-test");
}
#Autowired
#Spy
AService aService;
#Autowired
#InjectMocks
MyRESTController controller;
#Value("${local.server.port}")
int port;
#Before public void setUp(){
RestAssured.port = port;
MockitoAnnotations.initMocks(this);
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
doNothing().when(aService)
.doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
given()
.multiPart("file", file)
.multiPart("something", ":(")
.when().post("/file-upload")
.then().(HttpStatus.CREATED.value());
}
}
the service is defined as
#Profile("unit-test")
#Primary
#Service
public class MockAService implements AService {
//empty methods implementation
}
The error says the request is not a multi-part request. In other words at that point it's expected to have been parsed. However in a MockMvc test there is no actual request. It's just mock request and response. So you'll need to use perform.fileUpload(...) in order to set up a mock file upload request.

Unit testing with MockServletContext

I have set up spring boot application using Gradle. Now I do understand that #EnableAutoConnfiguration configures the application based on dependencies in a class path. I am pretty happy to avoid all of the plumbing but things start happening which I wish wouldn't.
Here are my dependencies:
dependencies {
compile('org.springframework.boot:spring-boot-starter-web:1.2.3.RELEASE')
compile 'org.springframework.hateoas:spring-hateoas:0.17.0.RELEASE'
compile 'org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'com.google.guava:guava:18.0'
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
compile 'commons-beanutils:commons-beanutils:1.9.2'
runtime 'org.hsqldb:hsqldb:2.3.2'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'com.jayway.jsonpath:json-path:2.0.0'
}
My application class:
#ComponentScan("org.home.project")
#SpringBootApplication
//#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
A snippet from UserController:
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<ResourceSupport> create(#Valid #RequestBody UserCreateRequest ucr, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw new InvalidRequestException("Bad Request", bindingResult);
Long userId = userService.create(ucr);
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(UserEndpoint.class).withSelfRel());
resource.add(linkTo(methodOn(UserEndpoint.class).update(userId, null, null)).withRel(VIEW_USER));
resource.add(linkTo(methodOn(UserEndpoint.class).delete(userId)).withRel(DELETE_USER));
return new ResponseEntity(resource, HttpStatus.CREATED);
}
The UserController.java has two annotations:
#RestController
#RequestMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE)
First of - notice the commented out #EnableHyperdiaSupport annotation - links in the ResourceSupport instance are still serialized to hal+json format despite media type produced and media type set in the request. This happens automatically when 'org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE' is introduced in the dependencies. How would one go about configuring it explicitly ?
Another issue are unit tests.
This passes:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MockServletContext.class)
#WebAppConfiguration
public class UserControllerTest {
...ommited for brevity...
#InjectMocks
private UserController testObject;
#Before
public void setUp() throws Exception {
initMocks(this);
mockMvc = standaloneSetup(testObject).build();
}
#Test
public void testUserCreatedLinks() throws Exception {
mockMvc.perform(post("/users").contentType(MediaType.APPLICATION_JSON).content(data))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$.links.[*].rel", hasItem("self")));
}
...ommited fro brevity...
}
The post request returns a standard JSON response in the test - not HAL+JSON. Is there a way to reconfigure this so that unit testing #RestController with MockServletContext would produce HAL+JSON or getting back to problem number 1 - how to configure the response format explicitly so that Jackson serializer would not produce hal+json ?
You're running your test using Spring MVC Test's standaloneSetup which uses a bare minimum of configuration to get your controller up and running. That configuration isn't the same as the configuration that will be used when you run the whole application.
If you want to use the same configuration, you could use webAppContextSetup:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class SomeTests {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
}
Alternatively, you can replicate Spring HATEOAS's configuration in the standalone setup. Note that this runs the risk of your tests' configuration deviating from your application's configuration. You'd create the MockMvc instance like this:
TypeConstrainedMappingJackson2HttpMessageConverter messageConverter =
new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
messageConverter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON));
ObjectMapper objectMapper = messageConverter.getObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
objectMapper.setHandlerInstantiator(
new Jackson2HalModule.HalHandlerInstantiator(new DefaultRelProvider(), null));
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testObject)
.setMessageConverters(messageConverter).build();

Register #ControllerAdvice annotated Controller in JUnitTest with MockMVC

My #ControllerAdvice annotated Controller looks like this:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
#ExceptionHandler(AuthenticationException.class)
public void authenticationExceptionHandler() {
}
}
Of course my development is test driven and I would like to use my exception Handler in the JUnit Tests. My Test case looks like this:
public class ClientQueriesControllerTest {
private MockMvc mockMvc;
#InjectMocks
private ClientQueriesController controller;
#Mock
private AuthenticationService authenticationService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void findAllAccountRelatedClientsUnauthorized() throws Exception {
when(authenticationService.validateAuthorization(anyString())).thenThrow(AuthenticationException.class);
mockMvc.perform(get("/rest/clients").header("Authorization", UUID.randomUUID().toString()))
.andExpect(status().isUnauthorized());
}
}
Probably I need to register the ControllerAdvice Class. How to do that?
Since Spring 4.2, you can register your ControllerAdvice directly into your StandaloneMockMvcBuilder:
MockMvcBuilders
.standaloneSetup(myController)
.setControllerAdvice(new MyontrollerAdvice())
.build();
In order for the full Spring MVC configuration to get activated, you need to use MockMvcBuilders.webAppContextSetup instead of MockMvcBuilders.standaloneSetup.
Check out this part of the Spring documentation for more details.
Your code would look like:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("test-config.xml")
public class ClientQueriesControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private AuthenticationService authenticationService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void findAllAccountRelatedClientsUnauthorized() throws Exception {
when(authenticationService.validateAuthorization(anyString())).thenThrow(AuthenticationException.class);
mockMvc.perform(get("/rest/clients").header("Authorization", UUID.randomUUID().toString()))
.andExpect(status().isUnauthorized());
}
}
Then inside test-config.xml you would add a Spring bean for AuthenticationService that is a mock.
<bean id="authenticationService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="your.package.structure.AuthenticationService"/>
</bean>
You could of course use profiles to inject the mock AuthenticationService in the tests if want to reuse your regular Spring configuration file instead of creating test-config.xml.
UPDATE
After digging around a bit, I found that StandaloneMockMvcBuilder returned by (MockMvcBuilders.standaloneSetup) is totally customizable. That means that you can plug in whatever exception resolver you prefer.
However since you are using #ControllerAdvice, the code below will not work.
If however your #ExceptionHandler method was inside the same controller the code all you would have to change is the following:
mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(new ExceptionHandlerExceptionResolver()).build();
UPDATE 2
Some more digging gave the answer to how you can register a correct exception handler when you are also using #ControllerAdvice.
You need to update the setup code in the test to the following:
#Before
public void setUp() throws Exception {
final ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
//here we need to setup a dummy application context that only registers the GlobalControllerExceptionHandler
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerBeanDefinition("advice", new RootBeanDefinition(GlobalControllerExceptionHandler.class, null, null));
//set the application context of the resolver to the dummy application context we just created
exceptionHandlerExceptionResolver.setApplicationContext(applicationContext);
//needed in order to force the exception resolver to update it's internal caches
exceptionHandlerExceptionResolver.afterPropertiesSet();
mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(exceptionHandlerExceptionResolver).build();
}
Got past the NestedServletException with the following solution...
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("exceptionHandler", GlobalControllerExceptionHandler.class);
final WebMvcConfigurationSupport webMvcConfigurationSupport = new WebMvcConfigurationSupport();
webMvcConfigurationSupport.setApplicationContext(applicationContext);
mockMvc = MockMvcBuilders.standaloneSetup(controller).
setHandlerExceptionResolvers(webMvcConfigurationSupport.handlerExceptionResolver()).
build();
If you have multiple advice classes, each with #ExceptionHandler and one of those classes is handling a very generic base exception, like #ExceptionHandler({Exception.class}), then you will need to add some priority ordering to your advice classes per this SO answer
https://stackoverflow.com/a/19500823/378151
If you are using junits older version than 5 and can not upgrade it for any reason then consider defining like below:
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("exceptionHandler", MyExceptionHandler.class);
final WebMvcConfigurationSupport webMvcConfigurationSupport = new WebMvcConfigurationSupport();
webMvcConfigurationSupport.setApplicationContext(applicationContext);
mockMvc = MockMvcBuilders.standaloneSetup(myController).
setHandlerExceptionResolvers(webMvcConfigurationSupport.handlerExceptionResolver()).
build();
You can add this to your test class
#Autowired
#Qualifier("handlerExceptionResolver")
void setExceptionResolver(HandlerExceptionResolver resolver)
{
this.exceptionResolver = resolver;
}
and then add the exceptionResolver to your MockMvc
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(this.exceptionResolver).build();
}

Categories