Mockito Test method parameters - spring-boot

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

Related

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.

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

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.

POST / GET Request Param Validation in Spring Boot

I am using spring boot. I want to validated the POST request params. So I have gine through #Validated annotation but this require creating a different class for Every API. How should I write my code?
As for example, this is my api
#RequestMapping("/tags/{tagId}/{tagParentId}")
public Response<Demo> a(#PathVariable int tagId, #PathVariable int tagParentId){
... code
}
#RequestMapping("/data/{courseId}/{instId}")
public Response<Demo> b(#PathVariable int courseId, #PathVariable int instId){
... code
}
How should I change my code to add params validation for there API's such that I do not need to create two different validation class? Just one class and then I can add different functions for different API's.
#Validated should be used, to check that a parameter is syntactical correct.
As you are using int values, this is already done by spring.
If tagId is not a valid int, the client will already receive a Http error code.
The validation, whether there is a tag with the given tagId is implicitly done in your code, you do not need an additional validator for that.
If you read tags for example from the database, and you cannot find a tag for the tagId, you should
return new ResponseEntity(HttpStatus.NOT_FOUND);
from your controller method.
You may need to change the return type of your controller method to a common superclass or just to Object, to allow returning the ResponseEntity.
Its also possible to throw exceptions in the controller methods and to configure spring to return a regarding HttpStatus.
See exception-handling-for-rest-with-spring

Feign Client with Spring Boot: RequestParam.value() was empty on parameter 0

I created a simple Feign Client with Spring Boot like this:
#FeignClient("spring-cloud-eureka-client")
public interface GreetingClient {
#RequestMapping("/greeting")
String greeting(#RequestParam String name);
}
But when I try just to start an application I get an error:
java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0
First I didn't understand what is the reason and googled a lot but didn't find an answer. Almost excidentely I figured out that it works if to write request param name explicitly:
#RequestParam("name") String name
So my question: is it a bug or could it be configured to not write request params names explicitly?
Both Spring MVC and Spring cloud feign are using the same ParameterNameDiscoverer - named DefaultParameterNameDiscoverer to find parameter name. It tries to find the parameter names with the following step.
First, it uses StandardReflectionParameterNameDiscoverer. It tries to find the variable name with reflection. It is only possible when your classes are compiled with -parameters.
Second, if it fails, it uses LocalVariableTableParameterNameDiscoverer. It tries to find the variable name from the debugging info in the class file with ASM libraries.
The difference between Spring MVC and Feign occurs here. Feign uses above annotations (like #RequestParam) on methods of Java interfaces. But, we use these on methods of Java classes when using Spring MVC. Unfortunately, javac compiler omits the debug information of parameter name from class file for java interfaces. That's why feign fails to find parameter name without -parameter.
Namely, if you compile your code with -parameters, both Spring MVC and Feign will succeed to acquire parameter names. But if you compile without -parameters, only Spring MVC will succeed.
As a result, it's not a bug. it's a limitation of Feign at this moment as I think.
Just use String greeting(#RequestParam("name") String name);
#FeignClient("spring-cloud-eureka-client")
public interface GreetingClient {
#RequestMapping("/greeting")
String greeting(#RequestParam("name") String name);
}
I use upgrade maven-compiler-plugin to solve this plobrem. you can access: https://blog.51cto.com/thinklili/2566864
This worked for me.
#FeignClient(name="session-service", url="${session.host}")
public interface SrocessingProxy {
#RequestMapping(value = "/process/{key}", method = RequestMethod.POST)
public Response processSession(#RequestParam String key, #RequestBody PayloadHolder payload);
}
//Service
#RequestMapping(value = "/process/{key}", method = RequestMethod.POST)
public Response processSession(#RequestParam String key, #RequestBody PayloadHolder payload) {
System.out.print("Key : " + key);
}

Spring Integration Java DSL - #ServiceActivator method with #Header parameter annotations

I have a Spring Integration 4 bean method with the following signature:
#Component
public class AService {
#ServiceActivator
public Message<?> serviceMethod(
Message<?> message,
#Header(ServiceHeader.A_STATE) AState state,
#Header(ServiceHeader.A_ID) String id) {
...
}
...
}
At the moment, I call this service method from within a Spring Integration Java DSL (spring-integration-java-dsl:1.0.1.RELEASE) flow like this:
.handle("aService", "serviceMethod")
This works absolutely fine but, I am wondering whether it is possible to call the service method in the following sort of way:
.handle(Message.class, (m, h) -> aService.serviceMethod(m, h))
The reason I would like to call the service method in this sort of manner is so that, when someone is looking at the code using an IDE such as Eclipse, they can drill into that service's method by for example highlighting the method and pressing F3.
So my question is, is there an alternative way to calling a #ServiceActivator method (which includes #Header annotations) without using strings for service name/service method in a .handle()?
Not exactly, you can't pass the whole message and selected headers, but you can pass the payload and individual headers...
.handle(String.class, (p, h) -> aService().serviceMethod(p,
(AState) h.get(ServiceHeader.A_STATE),
(String) h.get(ServiceHeader.A_ID)))
(assuming the payload is a String).
Note that the #Header annotations are meaningless in this scenario because you are directly pulling out the headers in the lambda expression.
It also doesn't need to be annotated as a #ServiceActivator; you can invoke any bean method that way.
public Message<?> serviceMethod(
String payload, AState state, String id) {
...
}
The
.handle("aService", "serviceMethod")
variation is where all the magic happens (matching the message contents to the method parameters). Of course, we can't do any "magic" when you want to invoke the method directly in Java.

Resources