unable to mock crudrepository save method - spring-boot

#ExtendWith(MockitoExtension.class)
class taskTestMain {
#InjectMocks
task task1;
#Mock
private statusRepository statusRepo;
#Mock
status statusRec;
#Test
void test() {
Mockito.when(statusRepo.save(statusRec)).thenReturn(statusRec);
task1.method();
}
}
class task
{
method()
{
statusRec = statusRepo.save(statusRec); //after this step I get Potential stubbing exception
}}
I have tried several ways . I am not able to figure this out. Please suggest how to proceed. Below I have pasted the junit error. task- Strict stubbing argument mismatch. Please check:
this invocation of 'save' method:statusRepo.save(status#8bffb8b);
-> at task.run(task.java:80)
has following stubbing(s) with different arguments: 1. statusRepo.save(status#26a2f7f9
); -> at .taskTestMain.test(taskTestMain.java:110)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
Thanks for your time

To be honest I'am not totally sure about this but I have a guess.
Mockito.when(statusRepo.save(statusRec)).thenReturn(statusRec);
With this stub you simply want return the first parameter passed into the save Method? If this is true there is a more generic way to do this.
when(statusRepo.save(any(TcMdhTotemConsensusStatus.class))).then(returnsFirstArg());
This stub will always return the first argument that is passed into your save
method.
See this stackoverflow post for more information. There also some alternatives in case your Java or Mockito Version does not match.

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

Mock a MDC data in spring boot test

I want to mock a data which is fetched from MDC in test class other wise when the code executed it return a null value. So i try below,
#RunWith(SpringRunner.class)
#SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOCK,classes = TestApp.class)
public class Test {
private MockMvc restMvc;
#Before
public void setUp() {
mock(MDC.class);
this.restMvc = MockMvcBuilders.standaloneSetup(TestController).build();
}
#Test
public void testMe() throws Exception {
when(MDC.get("correlation-id")).thenReturn("1234");
//Req param and header are intialized
restMvc.perform(get("/assignments").headers(headers).params(reqParams).principal(token).accept(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().is2xxSuccessful());
}
}
But i am getting error,
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
To mock a static method you should use Mockito.mockStatic method like this:
try (var mockStatic = Mockito.mockStatic(MDC.class)) {
mockStatic.when(() -> MDC.get("correlation-id"))
.thenReturn("1234");
// rest of the test...
}
You can read more about it in the Mockito documentation. Additionally, I've reproduced the problem and tested the solution - you can see a commit in a GitHub repository with all required code. Please note that usage of mockito-inline is required for this to work - see this part of the docs.
In case of your test, it would probably better to go with the approach proposed by #knittl in the comment - instead of mocking the get method, a value can be set in the MDC using the put method: MDC.put("correlation-id", "1234"). I've included both approaches in the GitHub repo mentioned before - both tests pass.

How can I verify repository invocation?

Say I have a repository interface looks like this,
#Repository
interface MyRepository {
Optional<My> findByOtherId(long otherId);
default Optional<My> findByOther(Other other) {
return findByOtherId(other.getId());
}
}
I'm trying to invoke findByOther and verifies that the call invokes findByOtherId method.
#DataJpaTest
class MyRepositoryTest {
#Test
void test() {
Other other = new Other(0L);
repository.findByOther(other);
verify(other, times(1)).getId(); // verified
verify(repository, times(1)).findByOtherId(other.getId()); // called, yet not verified!
}
#SpyBean
private MyRepository repository;
}
When I debug, the findByOtherId method is called. But mockito complains it doesn't.
How can I do this?
As far as I understand you are trying to mock a methodCall that is done from inside the SpyBean. You are essentially trying to verify a private method call (even though findByOtherId can have the public modifier). That is why Mockito complains. As far as I know, mockito creates proxys around its spies. Similar to how spring proxies its beans. I donĀ“t think what you are trying to achieve is solved that way.
For a solution, I would suggest looking into PowerMock. There might be a solution in there. https://www.baeldung.com/powermock-private-method
verifyPrivate(mock).invoke("saveIntoDatabase", ArgumentMatchers.anyString());

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.

Why i am getting null value when i am mocking or Inject the object .?

I am getting a null value when i am trying to Inject the Dao interface.
Sample of code here when i am writing a test cases.
#Mock
private ServiceUriLocator serviceLocator;
#Mock
private SessionDao sessionDao;
ActiveSession session;
AccountData accountdata;
#InjectMocks
Account account
This Above code when i am using inside Method like
#Test
public void testActivate()
{
Response response=account.activate(accountdata).
}
private Accountdata accountdata(){
accountdata.setFirstName("Employee_name");
accountdata.LastName("Employee_Last_Name");
return accountdata;
}
In above code account value is null that's why i am getting null pointer exception.
Anybody can calrify why i am getting null value for injecting the mock value.
There are many factors while executing a test case using mock frameworks.
Is Runner setup ? Which runner are you executing with?
It has to be MockitoJunitRunner that helps to run with Mocks
when statement is written on the same mock object ?
This has to match with same mock object
Is the input string/data match the when statement ?
This has to be same input
when(service.execute('hi')).thenReturn();
is different from
when(service.execute('hello')).thenReturn();
Where is the when written ? setup - #Before level or test method level ?
The Global statements written applies to all the test methods, so be careful while you make the when statements in the #Before method.
These are some of the debug points.

Resources