Testing Spring MVC annotation mappings - java

With Spring MVC, you can specify that a particular URL will handled by a particular method, and you can specify that particular parameters will map to particular arguments, like so:
#Controller
public class ImageController {
#RequestMapping("/getImage")
public String getImage( #RequestParam("imageId") int imageId, Map<String,Object> model ) {
model.put("image",ImageService.getImage(imageId));
}
}
This is all well and good, but now I want to test that an http request with an imageId parameter will invoke this method correctly. In other words, I want a test that will break if I remove or change any of the annotations. Is there a way to do this?
It is easy to test that getImage works correctly. I could just create an ImageController and invoke getImage with appropriate arguments. However, this is only one half of the test. The other half of the test must be whether getImage() will be invoked by the Spring framework when an appropriate HTTP request comes in. I feel like I also need a test for this part, especially as my #RequestMapping annotations become more complex and invoke complex parameter conditions.
Could you show me a test that will break if I remove line 4, #RequestMapping("getImage")?

You could use AnnotationMethodHandlerAdapter and its handle method programmatically. This will resolve the method for the given request and execute it. Unfortunately this is a little indirect. Actually there is a private class called ServletHandlerMethodResolver in AMHA that is responsible for just resolving the method for a given request. I just filed a request for improvement on that topic, as I really would like to see this possible, too.
In the meantime you could use e.g. EasyMock to create a mock of your controller class, expect the given method to be invoked and hand that mock to handle.
Controller:
#Controller
public class MyController {
#RequestMapping("/users")
public void foo(HttpServletResponse response) {
// your controller code
}
}
Test:
public class RequestMappingTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MyController controller;
private AnnotationMethodHandlerAdapter adapter;
#Before
public void setUp() {
controller = EasyMock.createNiceMock(MyController.class);
adapter = new AnnotationMethodHandlerAdapter();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
#Test
public void testname() throws Exception {
request.setRequestURI("/users");
controller.foo(response);
EasyMock.expectLastCall().once();
EasyMock.replay(controller);
adapter.handle(request, response, controller);
EasyMock.verify(controller);
}
}
Regards,
Ollie

Ollie's solution covers testing the specific example of an annotation but what about the wider question of how to test all the other various Spring MVC annotations. My approach (that can be easily extended to other annotations) would be
import static org.springframework.test.web.ModelAndViewAssert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({/* include live config here
e.g. "file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml" */})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
private MyController controller;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
// I could get the controller from the context here
controller = new MyController();
}
#Test
public void testFoo() throws Exception {
request.setRequestURI("/users");
final ModelAndView mav = handlerAdapter.handle(request, response,
controller);
assertViewName(mav, null);
assertAndReturnModelAttributeOfType(mav, "image", Image.class);
}
}
I've also written a blog entry about integration testing Spring MVC annotations.

Related

Is there a way to get request URI in Spring?

Whenever a request is made, I need to get the request URI for some internal calculations.
For some time I've been doing it like this:
public Mono<Response> example(ServerHttpRequest req) { ... }
And then using req.getURI(), but that becomes a pain once you need to pass it down multiple times. I need the URI object to extract scheme, schemeSpecificPart, host, port from it.
Is there a way to get these properties without extracting them from a request?
UPD: I see that for Web MVC there are convenient methods to retrieve request URI. But I need the same for reactive stack (netty).
It can be achieved by creating WebFilter that puts ServerHttpRequest into the Context:
#Component
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveRequestContextFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
return chain
.filter(exchange)
.contextWrite(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));
}
}
Additionally, create a class that provides static access to request data:
public class ReactiveRequestContextHolder {
public static final Class<ServerHttpRequest> CONTEXT_KEY = ServerHttpRequest.class;
public static Mono<ServerHttpRequest> getRequest() {
return Mono.deferContextual(Mono::just).map(ctx -> ctx.get(CONTEXT_KEY));
}
public static Mono<URI> getURI() {
return getRequest().map(HttpRequest::getURI);
}
}
Methods can be accessed through the class name directly without having to instantiate them. Just be aware that it should not be accessed before the filter is executed.
Example of usage:
#RestController
#RequestMapping
public class TestController {
#GetMapping("/test")
public Mono<URI> test() {
return ReactiveRequestContextHolder.getURI();
}
}
Reference
You can try this :
public Mono<Response> example(WebRequest request){
System.out.println(request.getDescription(false));
.......
}
You can turn this false to true in getDescription as false will only give you the Uri which i think is the only thing you need.
You can inject it in any bean.
#Autowired
private HttpServletRequest request;

ConstraintValidator always has null value during test

after fixing a bug in an older Java Spring MVC 4.1 application, I wanted to add a unit test, but the method the current code base is using for testing won't actually execute validation.
So I wanted to add MVCMock, but when it executes the validation methods, the values passed to isValid is always null.
Relevant files below (I've tried to strip out as much noise as possible):
// Unit Test
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#EnableWebMvc
#ContextConfiguration(locations = {"/applicationContext-test.xml"})
public class ExampleControllerTest extends AbstractControllerTestBase {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(this.context).build();
}
#Test
public void fileUploadZipArchive() throws Exception {
// Upload a zip file
File mockFile = new File("src/test/resources/fixtures/ex.zip");
MockHttpServletRequestBuilder multipart = MockMvcRequestBuilders
.fileUpload("/files/ex/upload/Tex")
.file("ex.zip", FileUtils.readFileToByteArray(mockFile));
MvcResult result = mockMvc.perform(multipart)
.andReturn();
}
// Bean
public class FileUploadBean {
#Valid
#MultipartMimeTypeMatch
private MultipartFile file = null;
// ...
}
// Validator
public class MultipartMimeTypeMatchValidator implements ConstraintValidator<MultipartMimeTypeMatch, Object> {
// ...
public boolean isValid(Object value, final ConstraintValidatorContext context) {
// value and context is always null
}
}
// Controller
#RequestMapping(value = "/files/{ex1}/upload/{ex2}", method = RequestMethod.POST)
public Object uploadFile(HttpServletRequest request, #PathVariable String ex1,
#PathVariable String ex2, #Valid FileUploadBean fileUploadBean, BindingResult result) throws IllegalStateException, IOException {
// ...
}
}
What could be going wrong?
NOTE: Spring 4.1
The javadoc of the file method states that the name should be the name of the file. I agree that that is a bit misleading. Instead it should be the name of the request parameter to use, which should be (generally speaking) the same as the property in your model object.
.file("ex.zip", FileUtils.readFileToByteArray(mockFile));
With this a request parameter named ex.zip will be part of the request, however you have one that is named file.
.file("file", FileUtils.readFileToByteArray(mockFile));
Using the above line should fix it and properly bind to your object in turn properly invoking your validator.
On a side node, your validator should properly handle the null case as well or add a #NotNull on the field as well. The #Valid on the field doesn't do anything so you can remove that.

NullPointerException while trying to test a Spring MVC controller method with JUnit, Mockito

I am trying to unit test a Spring MVC controller method, but my unit test keeps failing.
I have a Spring MVC controller method:
// MyController.java
#RequestMapping(value = "/end-point", method = RequestMethod.GET)
public ModelAndView getData(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
ModelAndView mv = new ModelAndView();
DataManager dataManager = DataManager.getInstance();
DataUser currentUser = (DataUser) request.getSession().getAttribute("currentUser");
List<DataItem> dataList = dataManager.getDataForUser(currentUser.getId());
mv.addObject("dataList", dataList);
mv.setViewName("home-page");
return mv;
}
I am trying to test this controller method with JUnit. I have very little experience unit testing and am trying to learn. Seems like this is near-impossible or does not make sense to do without a mocking library, and the project I'm working on already has Mockito as a dependency so I'm trying to use that. My test class is below:
//MyControllerTest.java
public class MyControllerTest {
#InjectMocks
private MyController myController;
#Mock
HttpServletRequest request;
#Mock
HttpServletResponse response;
#Mock
ModelAndView mockModelAndView;
#Mock
DataManager mockDataManager;
#Mock
DataUser mockDataUser;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void getDataTest() throws Exception {
//I guess I have to somehow mock mockDataUser here even more than #Mock???
Mockito.when(request.getSession().getAttribute("currentUser")).thenReturn(mockVendorUser); // <-- this is where the null pointer exception is coming from
Mockito.when(myController.getData(request, response)).thenReturn(mockModelAndView);
ModelAndView testing = profileControllerWH.getMySkus(request, response);
assertEquals(1, 1);
}
}
When I run my test, it fails and I get a java.lang.NullPointerException exception in the console, specifying the line above with the null pointer exception comment.
I have tried looking up how to mock classes with Mockito, and I keep seeing the #Mock annotation, which I already have as #Mock DataUser (as well as other classes I am using in the controller method that I guess I need to mock).
How do I get this to work? It seems like I have to create a whole new DataUser object with fake data, but then that seems to defeat the purpose of the mocking library. What would the point of using Mockito be if I have to create my own objects with fake data? Or, I might be misunderstanding this because of lack of experience.
Remember that by default, unstubbed methods return default value of return type when called (0 for numbers, null for Objects).
You haven't stubbed request.getSession() so it returns null.
You need to:
provide a mock for session
stub request.getSession() to return this mock
stub session.getAttribute("currentUser")
On top of that:
While calling the controller method in your test certainly has value of testing the method body, but you will test more functionality (like request and response serialization) if you re-implement your test as a #WebMvcTest

Mockito - Unmocked method failed to return the object itself

I have a Spring #RestController that has a field of Apache Camel interface FluentProducerTemplate.
I am testing the controller with MockMvc and I am injecting FluentProducerTemplate as a mock.
I would like to mock only one method - request(), and use the real implementation of the other methods.
However, I get NullPointerException from the unmocked methods. Other FluentProducerTemplate methods n , their return type is FluentProducerTemplate. In the implementation they return this. The mock object returns null.
I thought that mockito #Mock mocks only the methods that I specify. Other methods use the original implementation. Is this a correct statement?
I tried #Spy instead of #Mock and I got the same error.
When I mock all the methods that I work with then it works and no NullPointerException.
Code:
REST Controller:
#RestController
#RequestMapping("/v1/test”)
public class MyController {
#EndpointInject(uri = "direct:main")
private FluentProducerTemplate producerTemplate;
#RequestMapping(value = “/test2”, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public MyResponse testRequest(
#RequestHeader(“id”) String id,
#RequestHeader(“context”) String context,
#RequestBody RequestBody requestBody
) {
MyResponse response = producerTemplate
.withHeader(“id”, id)
.withHeader(“context”, context)
.withBody(requestBody)
.request(MyResponse.class);
return response;
}
Test:
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
private MockMvc mockMvc;
#Mock
private FluentProducerTemplate producerTemplateMock;
#InjectMocks
private MyControllerTest myController;
private static MyResponse expectedResultSuccess;
private static String requestString;
private static HttpHeaders allRequestHeaders;
#BeforeClass
public static void setup() {
allRequestHeaders = new HttpHeaders();
allRequestHeaders.set(“id”, “123”);
allRequestHeaders.set(“context”, “ABCD1234”);
allRequestHeaders.set(“Content-Type”, “application/json”);
expectedResultSuccess = new MyResponse(“test”);
requestString = “request”BodyText;
}
#Before
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
when(producerTemplateMock.request(any())).thenReturn(expectedResultSuccess);
}
#Test
public void testSuccess() throws Exception {
mockMvc.perform(post(“/v1/test/test2)
.headers(allRequestHeaders)
.content(requestString))
.andExpect(status().isOk())
}
}
The test pass only when I add the below to init():
when(producerTemplateMock.withHeader(any(), any())).thenReturn(producerTemplateMock);
when(producerTemplateMock.withBody(any())).thenReturn(producerTemplateMock);
My main question is - why do I have to mock all methods?
I prefer to use the original implementation of withHeader() and withBody() and mock only request().
You want so called partial mocks. Depending on whether you want to set up mostly mocks or mostly call real implementations there are different prefered approaches.
1. spy for few mocks, mostly real implementation
If you want to mock only some methods and otherwise call the real implementation:
FluentProducerTemplate producerTemplateMock = spy(FluentProducerTemplate.class);
// Mock implementation
doReturn(expectedResultSuccess).when(producerTemplateMock).request(any());
// All other method call will use the real implementations
2. mock for mostly mocks, few real implementations
FluentProducerTemplate producerTemplateMock = mock(FluentProducerTemplate.class);
// Mock methods
when(producerTemplateMock.request(any())).thenReturn(expectedResultSuccess);
// tell mockito to call the real methods
when(producerTemplateMock.withHeader(any(), any())).thenCallRealMethod;
when(producerTemplateMock.withBody(any())).thenCallRealMethod();
As you can see, the 2nd approach is more boilerplate to write. However, it depends on your use case what approach is better suited.

Spring Data Rest Custom Controller With with content-Type:"text/plain"

I have been trying to write a custom functionality where a REST end point consumes a plain-text . Here is the piece of code:
#RepositoryRestController
#RequestMapping("/api/samples")
public class SampleController {
#Autowired
SampleRepository sampleRepository;
#RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public void updateSample(#PathVariable Long id, #RequestBody String message ) {
//do some custom logic here
//sampleRepository.update(id);
}
}
Spring Data rest throws a exception HttpStatus.UNSUPPORTED_MEDIA_TYPE(415). However when I move the code to a separate Controller:
#RestController
#RequestMapping("/api/samples")
public class SampleController {
#Autowired
SampleRepository sampleRepository;
#RequestMapping(value = "/{id}", method = RequestMethod.PUT,consumes =MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public void updateSample(#PathVariable Long id,#RequestBody String message ) {
//do some custom logic here
//sampleRepository.update(id);
}
}
This works Fine . Has Anyone faced similar issues, moving the code to a separate method resolves the issue(Work Around).
Any Suggestion on how to approach the problem would be great
The only difference I see between your two code samples is the use of #RepositoryRestController in the first case versus #RestController in the second case.
When overriding endpoint that are auto-generated by Spring Data REST, such as PUT /api/samples/{id}, you should use #RequestMapping at the method level, for reasons exposed here.
The official documentation does not explicitly forbidding class-level requestmapping, but the only example given it uses method-level requestmapping.
#RepositoryRestController
public class SampleController {
#Autowired
SampleRepository sampleRepository;
#RequestMapping(value = "/api/samples/{id}", method = RequestMethod.PUT, consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public void updateSample(#PathVariable Long id, #RequestBody String message ) {
//do some custom logic here
//sampleRepository.update(id);
}
}
If you use both #RepositoryRestController and #RequestMapping at the class level, you lose access to all endpoint handlers that Spring Data Rest usually generates. My guess is you're sending a request to /api/samples/{id} without setting the content type to text/plain, so there is actually no endpoint that can handle your request, hence the error message.
A remark regarding the seconde code sample: #ResponseBody is implied by #RestController, so there is no need to annotate your method with it.

Categories

Resources