MockMvc access the MockHttpServletRequest object that mockMvc will use - spring

Is there anyway to get a hold of the actual request object that mockMvc will use when you execute:
mockMvc.perform(RequestBuilder requestBuilder)
I know that I can build the request myself (i.e)
Integer id = new Integer(1);
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/myPath").param(Constants.ACTION, Constants.GET_DETAIL)
.param(Constants.ID, id.toString());
MockHttpServletRequest request = requestBuilder.buildRequest(wac.getServletContext());
but I cannot pass this request along as the mockMvc.perform method specifically only accepts the builder which will create a new instance of MockHttpServletRequest. I am using EasyMock which uses equals() in it's matchers (at least by default) and due to the lack of an equals() implementation in MockHttpServletRequest it simply compares the object ids. i.e
EasyMock.reset(localeHelper);
localeHelper.getLocale(request);
EasyMock.expectLastCall().andReturn(locale);
/* this matcher will always fail because the request object is rebuilt by the mockMvc.perform(requestBuilder) call
and MockHttpServletRequest does not have an equals() method that these mocking tools can fall back on for object equivalency */
EasyMock.replay(localeHelper);

You probably want a capture.
Capture<HttpServletRequest> capture = EasyMock.newCapture();
EasyMock.expect(localeHelper.getLocale(EasyMock.capture(capture))).andReturn(locale);
EasyMock.replay(localeHelper);
mockMvc.perform(requestBuilder);
HttpServletRequest request = capture.getValue();
// Then assert whatever you want on the `request` that was received in parameter by the mock

Related

How to initialize Jackson on Spring Boot start to have fast 1st request?

Problem
I have a simple Spring Boot app with a basic RestController (full code available here). It consumes JSON and uses Jackson to convert request from JSON and response to JSON.
#RestController("/")
#RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public class SomeController {
#Autowired
private SomeService someService;
#PostMapping
public ResponseEntity<SomeResponseDto> post(#RequestBody #Valid SomeRequestDto someRequestDto) {
final SomeResponseDto responseDto = new SomeResponseDto();
responseDto.setMessage(someRequestDto.getInputMessage());
responseDto.setUuid(someService.getUuid());
return ResponseEntity.ok(responseDto);
}
After start-up, the 1st request is about 10-times slower than any sub-sequent request. I debugged and profiled the app and it seems that on first request a Jackson JSON parser is getting initialized somewhere in AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters and AbstractJackson2HttpMessageConverter.
In sub-sequent requests, it seems to get re-used.
Question
How do I initialize Jackson JSON parsing during start-up so that also 1st request is fast?
I know how to trigger a method after Spring started. In PreloadComponent I added as an example how to do a REST request against the controller.
#Component
public class PreloadComponent implements ApplicationListener<ApplicationReadyEvent> {
private final Logger logger = LoggerFactory.getLogger(PreloadComponent.class);
#Autowired
private Environment environment;
#Autowired
private WebClient.Builder webClientBuilder;
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// uncomment following line to directly send a REST request on app start-up
// sendRestRequest();
}
private void sendRestRequest() {
final String serverPort = environment.getProperty("local.server.port");
final String baseUrl = "http://localhost:" + serverPort;
final String warmUpEndpoint = baseUrl + "/warmup";
logger.info("Sending REST request to force initialization of Jackson...");
final SomeResponseDto response = webClientBuilder.build().post()
.uri(warmUpEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.body(Mono.just(createSampleMessage()), SomeRequestDto.class)
.retrieve()
.bodyToMono(SomeResponseDto.class)
.timeout(Duration.ofSeconds(5))
.block();
logger.info("...done, response received: " + response.toString());
}
private SomeRequestDto createSampleMessage() {
final SomeRequestDto someRequestDto = new SomeRequestDto();
someRequestDto.setInputMessage("our input message");
return someRequestDto;
}
}
This only works in this toy example. In reality, I have many REST endpoints with complex DTOs and I would need to add a "warm-up" endpoint next to each "real" endpoint as I can't call my real endpoints.
What I already tried?
I added a second endpoint with a different DTO and called it in my PreloadComponent. This doesn't solve the problem. I assume that an Jackson / whatever instance is created for each type.
I autowired ObjectMapper into my PreloadComponent and parsed JSON to my DTO. Again, this doesn't solve the issue.
Full source available at: https://github.com/steinsag/warm-me-up
It turns out that Jackson validation is the problem. I added the JVM option
-verbose:class
to see when classes get loaded. I noticed that on 1st request, there are many Jackson validation classes getting loaded.
To confirm my assumption, I re-worked my example and added another independent warm-up controller with a distinct DTO.
This DTO uses all Java validation annotations also present like in the real DTO, e.g. #NotNull, #Min, etc. In addition, it also has a custom enum to also have validation of sub-types.
During start-up, I now do a REST request to this warm-up endpoint, which doesn't need to contain any business logic.
After start-up, my 1st request is now only 2-3 times slower than any sub-sequent requests. This is is acceptable. Before, the 1st request was 20-40 times slower.
I also evaluated if really a REST request is needed or if it is sufficient to just do JSON parsing or validation of a DTO (see PreloadComponent). This reduces runtime of 1st request a bit, but it is still 5-15 times slower than with proper warm-up. So I guess a REST request is needed to also load other classes in Spring Dispatcher, etc.
I updated my example at: https://github.com/steinsag/warm-me-up
I believe, that a lot of classes will be lazy-loaded. If first call performance is important, then I think warming up by calling each endpoint is the way to go.
Why do you say, that you cannot call the endpoints? If you have a database and you don't want to change the data, wrap everything in a transaction and roll it back after the warm up calls.
I haven't seen any other method to solve this, which doesn't necessarily mean, that it doesn't exist ;)

How to mock a builder pattern expression using mockito

I have following piece of Elasticsearch code
SearchRequestBuilder builder = client.prepareSearch(indexRange).setTypes(module).setSize(json.getPageSize())
.setFrom(json.getOffset()).setFetchSource(prepareFieldListToLoad(), prepareFieldListToIgnore())
.setExplain(false);
How can i mock it for junit testing using mockito?
It isn't clear from this snippet how client gets instantiated. However that happens, you need to mock that client object. Once you have that mocked, you can set the expectations on it so that when its prepareSearch method is invoked, it returns something--likely another mock. And then you will need to set the expectations on that other mock returned by the prepareSearch call, and so on.
You'll end up needing many mocks due to the chaining:
a mock for client
a mock for the object returned by the prepareSearch call
a mock for the object returned by the setTypes call
a mock for the object returned by the setSize call
a mock for the object returned by the setFrom call
a mock for the object returned by the setFetchSource call
a mock for the object returned by the setExplain call
You could consider using a stub for the object returned by the prepareSearch call if those "set" methods are just simple setters. Then the test will call the real setters of the object, but it still allows you to mock out some other call on the object that is more extensive.
Or if that sounds like too much manual setup, you can use Mockito's RETURN_DEEP_STUBS:
https://www.javadoc.io/doc/org.mockito/mockito-core/2.6.9/org/mockito/Answers.html
Example of deep stubs from: https://idodevjobs.wordpress.com/2015/04/09/mockito-deep-stubs-example/
public class MockingMethodChains {
#Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Foo foo;
#Test
public void easyWayToMockMethodChaining(){
when(foo.getBar().getName()).thenReturn("Foo Bar");
assertEquals("Foo Bar", foo.getBar().getName());
}
}
Other sources:
http://jakegoulding.com/blog/2012/01/09/stubbing-builder-pattern-in-mockito/
How to mock a builder with mockito

Unit testing with MockitoJunitRunner: thenReturn always returns null for any(...) matchers

Using Spring Boot 2.1.6.RELEASE. In an unit test with MockitoJunitRunner, I'm mocking a REST controller as follows:
#Mock
private MyController myController;
Then, I'm defining the expectations of the endpoint call as follows:
when (myController.myEndpoint(any(MyInputDto.class), any(OAuth2Authentication.class))).thenReturn(new ResponseEntity<MyOutputDto>(myOutputDto, HttpStatus.OK));
But the following call:
ResponseEntity<MyOutputDto> resp = myController.myEndpoint(any(MyInputDto.class), any(OAuth2Authentication.class));
assertNotNull(resp);
raises java.lang.AssertionError as resp is null. Why is that ? Of course, the two parameters are different instances between the when(...) clause and the call (probably both null), but my understanding is that they don't have to match as I'm using any(...) matcher, meaning that whatever these instances are, the return should be the same.
What am I missing here ?
You defined your mocked object to return a new ResponseEntity when its myEndpoint method is called with two parameters: any object of type MyInputDto and any object of type OAuth2Authentication.
But when you actually call the method using
ResponseEntity<MyOutputDto> resp = myController.myEndpoint(any(MyInputDto.class), any(OAuth2Authentication.class));
you don't call it with objects of those types, but with any matchers. That case wasn't mocked, and it certainly wasn't your intention to mock matcher arguments.
Calling with actual objects instead, like
ResponseEntity<MyOutputDto> resp = myController.myEndpoint(new MyInputDto(), new OAuth2Authentication());
would work, but that would not provide what you want to achieve by running a mocked test: it would just check if the mock returns what you defined, but it would't test your controller behaviour.
What you should do instead:
Don't mock the class you want to test; mock its dependencies.
private MyController myController;
#Mock
private MyInputDto inputDto;
#Mock
private OAuth2Authentication authentication;
// mock all method calls that are needed on dependencies
when(inputDto.someMethod(...)).thenReturn(...);
[...]
when(authentication.someMethod(...)).thenReturn(...);
[...]
Now you can test the actual behaviour of your test object:
ResponseEntity<MyOutputDto> resp = myController.myEnpoint(inputDto, authentication);
assertNotNull(resp);
A nice Mockito tutorial: https://www.vogella.com/tutorials/Mockito/article.html
Mockito even hints at something being wrong when trying to implement the test like you did in your question. Running your test I get
[MockitoHint] MockitoTest.test (see javadoc for MockitoHint):
[MockitoHint] 1. Unused... -> at MockitoTest.test(MockitoTest.java:24)
[MockitoHint] ...args ok? -> at MockitoTest.test(MockitoTest.java:26)
Line 24 refers to the line where the mock was stubbed (the when(...).thenReturn() part) because it is never used; line 26 referes to the calling line because it uses Matchers instead of objects.
You should see a similar output on your console when running the test.

Mockito Test method parameters

I have started newly working on testing using mockito. I have 2 questions ...
1. Question
I have method like below with optional and must have parameter so when I call this service method without the must params it should throw Exception.
#RequestMapping( method=RequestMethod.GET, produces={"application/xml", "application/json"})
public ResponseEntity<PResponse> get(#RequestParam(value="params1",required=false) String params1,
#RequestParam(value ="params2",required=false) String params2,
#RequestParam(value= "params3",required=true) String params3,
#RequestParam(value="refresh",required=false) boolean refresh,
#RequestParam(value="params4",required=true) List<String> params4)
{method logic ...}
Here params1,2,refresh are optional and params3,4 are must so when i get request with out params3,4 it should give an error. I am trying to write a test for this using mockito
#Test(expected = RuntimeException.class)
public void throwExceptionIfMissingParams34() throws RuntimeException {
when(myService.get(argThat(new MessagesArgumentMatcher()))).thenThrow(new RuntimeException()) ;
}
I am getting error saying get() in myService can't be applied to expected Parameters:, Actual Arguments:
2. Question :
In the above get method I am calling other method which calls other service method to get data from DB
List<Product> lstProduct = productComponent.readProduct(params3,params4);
which calls
Product product = productReader.readProduct(params3, params4, Product.class);
where in ProductReader.java Service class it gets data from DB by running query. I am trying to test the
List lstProduct = productComponent.readProduct(params3,params4);
in get() method so I tried mocking the Service Object but getting NullPointer Exception when I run the test.
Ad 1. Question
#RequestParam is an annotation from Spring Framework. It's used to define parameters for Controllers. The annotation is used by Spring to map the web request params to arguments which your controller accepts. Testing this behaviour would be actually testing Spring itself. I wouldn't do that. Another thing is, are you really testing a Service, or rather a Controller?
Besides, Java doesn't have the possibility to invoke a method with different arguments than defined, the only possibility is to use varargs.
Ad. Question 2
You didn't specify form where you are getting the NPE. But a first guess would be that you didn't configure Mockito correctly. For example take a look at: NullPointerException in mockito unit test

Mock controller with an object parameter

I'm trying to test a method with this signature:
#RequestMapping(value="/Employee/{id}", method=RequestMethod.PUT, consumes="application/json")
#Transactional
public #ResponseBody Map update(#PathVariable Integer id,
#RequestBody HashMap<String, Object> information) {
}
The problem is that MockMvc param attributes accept only String parameters, is there a way to pass a HashMap or an instance class object to the RequestBody as parameter?
When I try to pass a HashMap as a string, I get a MismatchException.
You need to use Jackson for this. The idea is to deserialize your objects (doesn't matter that it's HashMap) into JSON string and pass it into MockMvc.
Here is tutorial how to do that. Just search for TestClass there and take a look how it is used. Skip the unit testing of GET requests.

Resources