Is it a good practice to unit test controller classes in Spring MVC - spring

I used to unit test my controller classes just like any other normal Java class. I mean without using Spring's MockMvc. But then I realized that this way I won't be able to be sure that I've set up the MVC configuration correctly. So if I have controller like this:
#Restcontroller
#RequestMapping("/cars")
public class CarController{
private CarService carService;
public CarController (CarService service){this.carService = service};
#GetMapping
public List<Car> getCar(#RequestParam("filter") String filter){
if(filter!=null && !filter.trim().equal("")){
//omitted for brevity
}
}
}
and if I unit test its getCar method directly, even if the test passes, that won't mean my controller is alright. So instead of unit testing, I started actually doing integration testing. Something like this:
mockMvc.perform(get("/v1/cars?filter = Honda")).... bla bla bla
Recently a question arose if we should first unit test and the integration test rest controllers. At first glance, it seems to me that integration test does, in the end, check the correct behavior of the controller. But on the other hand, how good is that to rely on integration test only.

I personally never found unit testing controllers useful. In an ideal case a Controller is relatively slim, as it only calls a few methods of service objects and returns a result. IMO unit testing would mean (over)using the verify() method. (I.e. did the controller call the service method with the correct arguments.)
For example in my case a well written controller method looks like this:
#LoggingInfo
#PostMapping(value = "/someRoute",
produces = "application/json",
consumes = "application/json")
#ApiOperation(value = "Some description", response = SomeDTO.class)
public #ResponseBody
CompletableFuture<ResponseEntity> someControllerMethod(
#Validated(ValidationSequence.class) #RequestBody SomeDTO someDTO) {
return service
.doSomething(someDTO)
.thenApply((String var) -> ResponseEntity.ok(ResponseDTO.builder()
.myField(Collections.singleton(var)).build()));
}
To what extent would unit testing this method add value to the application?
For me, the game-changer was the use of integration tests. Here it turns out if all the spring magic is working correctly, such as:
Validators that were triggered by annotations
The order of (different) validators
The converters (i.e. see Jackson in action)
Exception handlers (are the thrown exceptions actually caught by the annotated exception handlers)
Hope this helps.

Related

Cannot understand mocks

I'm trying to write tests for my Spring Boot application that has some end points, it's a REST application.
I have the "usual" simple web application with a controller, a service and a repository. CRUD operations.
In my update endpoint I call the service layer to perform the update, like this:
#PutMapping
public Post updatePost(#RequestBody Post post) {
return postService.updatePost(post);
}
The updatePost method on the PostService class makes some checks about the object before updating in it, and if the checks all pass, then the update operation is perforrmed, like this:
public Post updatePost(Post post) {
if (post == null || post.getId() == null) {
throw new PostGenericException();
}
Post postToUpdate = postRepo.findById(post.getId()).orElseThrow(PostGenericException::new);
bool isOk = true;
// some other checks..
if (!isOk) {
throw new PostGenericException();
}
// update operation
postToUpdate.setMessage(post.getMessage());
....
return postRepo.save(postToUpdate);
}
From what I've seen online in the test class I have to do something like this:
#WebMvcTest(PostController.class)
public class PostControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper mapper;
#MockBean
private PostService postService;
#Test
public void updatePost() throws Exception {
Post post = new Post(...);
Mockito.when(postService.updatePost(post)).thenReturn(post);
mockMvc.perform(MockMvcRequestBuilders.put("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(this.mapper.writeValueAsString(post)))
.andExpect(status().isOk())
.andExpect(jsonPath("$", notNullValue()));
}
}
So here in the test method I'm completely replacing the logic of the updatePost method of the service layer with a fake one.
How is this useful?
The only good reason I can think of is that here I'm trying to test the endpoint by itself, meaning that I simply want to check if I can reach that endpoint, but I don't really care about the implementation at all, i.e how the update operation is performed. I'm expecting that if I make a PUT request to that endpoint I get a result, if the test fails I know that the controller doesn't handler that endpoint anymore.
Is this all about it or am I missing something?
If I remember correctly, Kent Beck also said that you don't want to test the implementation but only the public APIs, it doesn't make much sense to test the implementation anyway, you could have a lot of tests and at some point have more test code than production code.
Using mocks may be against your testing philosophy, but looking at the practicalities:
Web layer is a nice chunk to be tested separately. There is a good amount of things that you can check:
routing
request and response deserialization
validation
error handling
authentication
This also allows business logic tests to skip these concerns.
Additional benefits:
they are easy to set up and run on a single machine (or even single process)
they are reasonably fast

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.

spock, mock a method response in a spring bean

I have an integration test written in groovy (spock) in spring boot application. One of the application beans is called Validator it has the follwoing method:
public void validateIssueDates(final List<Timestamp> issueDates) {
issueDates.forEach(issueDate -> {
final Timestamp now = Timestamp.valueOf(LocalDateTime.now());
if (issueDate.before(now)) {
throw new IllegalArgumentException("Issue date is before current date");
}
});
}
In the Validator class there are other methods. In my spock integration test I would like to mock response for that particular method only. In the following way:
Validator.validateIssueDates(_) >> null
I want other validations to take place, but not this one. Bascially I want to achieve this but with spock. I would like to eliminate the validateIssueDates() method from being executed
solution using Spock
It's done using [#SpringSpy][2].
First we annotate field with a spring bean we want to wrap in spy object. For example:
#SpringSpy
private CarValidator carValidator;
then in our test, in then part we define how we want to override method from a a bean/spy:
then:
3 * carValidator.validateIssueDates(_) >> null
Solution using Mockito (as an additional approach, it's not related to spock solution)
I have got that pretty easy using spy in Mockito. Despite many trials (and errors) with spock's spy, It just doesn't want to work. If I get that, I post it here. For now, I can only share Mockito solution:
#Profile("test")
#Configuration
public class BeanConfig {
#Bean
#Primary
public CarValidator getCarValidatorSpy(CarValidator validator) {
CarValidator carValidatorSpy = Mockito.spy(validator);
Mockito.doNothing().when(carValidatorSpy).validateIssueDates(Mockito.any(CarDto.class));
return carValidatorSpy;
}
}
That's all. Seems fairly straightforward.

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

Mocking a constructor of #Autowired service(system under test)

I have to mock jerseyclient which is being created in Constructor of subjected service. Subjected service is System under test injected via Spring's #Autowired.
In constructor of the service client=client.create() method is written. We can't change this code(Although this is a code smell). I want to mock the jersey client but it is in constructor of the service. I am not able to mock this
sooo... long story short.. admitting you use mockito, in your src for test you should have an applicationcontext for your test.. usually we define one programmatically so, something along those lines..
import the .xml file you use for test purpose (in my case i imported the one for the mailserver, for the connection and for the authentication) instead of the one i use for the "local" environmnet. After then define a method to setup each and every of your service.
You might need to add a mock for your template resolver as well, but ultimately this all depends on your stack...
So based on your approach the final thing might be a bit different, but ultimately you're gonna do something along the lines of what i outline below:
#Configuration
#ImportResource(value = {
"classpath:applicationContext-jdbc-test.xml",
"classpath:applicationContext-ldap-test.xml",
"classpath:applicationContext-mail-test.xml"})
public class ApplicationTestContext {
#Bean
public ObjectMapperWrapper objectMapperWrapper() {
return Mockito.mock(ObjectMapperWrapper.class);
}
#Bean
public YourService yourService() {
return new YourServiceImpl();
}
}

Resources