Mocked repository does not trigger as expected

240 views Asked by At

I have a Controller Unit test using Mockito and MockMvc.

After a POST request, the POSTed object is resolved correctly, but my repository mock is not triggered.

Here is the mock code:

    Date mydate = new Date();
    Notification not = new Notification();
    not.setId(-1L);
    not.setUserid("BaBlubb");
    not.setTimestamp(mydate);
    not.setContent("MyContent");

    Notification not2 = new Notification();
    not2.setId(1L);
    not2.setUserid("BaBlubb");
    not2.setTimestamp(mydate);
    not2.setContent("MyContent");

    when(notificationRepository.save(not)).thenReturn(not2);

So this really just should simulate the saving of an object (ID is set and a route is generated out of it).

Unfortunately, the repository always returns null, so my code later fails when trying to return a new created Route out of null.

The mocks are correctly injected and do work for e.g. String comparison or if I only check for the function to have been called, I just can't get it to trigger on Objects.

The same issue occurs on

        verify(notificationRepository, times(1)).save(not);

it does not trigger.

The questions would be: 1.) Why does the mock not trigger? I suppose it does not check for value equality in the object, but for object identifiers, which are not the same since the object is serialized and de-serialized in between.

2.) How can I get a generic mock? e.g. whenever repository.save() is called, no matter the parameter, it always should perform a specific way, e.g. instead of

when(notificationRepository.save(not)).thenReturn(not2);

I'd like to have

when(notificationRepository.save()).thenReturn(not2);

P.S. If for whatever reason you need the rest of the code, here is the submitted part, object is the json representation of the notification (with jackson)

 mockMvc.perform(post("/api/notification").content(object)
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON));

and here is the Controller header, the Object is de-serialized perfectly, the values are 1:1 the same

 @RequestMapping(method=RequestMethod.POST)
    public ResponseEntity<?> postNotification(@RequestBody Notification n) {
        logger.debug("Saving userid "+n.getId());

Thanks for helping.

2

There are 2 answers

0
Jeff Bowman On BEST ANSWER

1.) Why does the mock not trigger? I suppose it does not check for value equality in the object, but for object identifiers, which are not the same...

By default, Mockito delegates to your object's equals method. If you haven't overridden that, then it checks references by default. The following two lines are equivalent:

when(notificationRepository.save(not)).thenReturn(not2);
when(notificationRepository.save(not)).thenReturn(eq(not2)); // uses eq explicitly

If all Notification objects with the same fields are the same, then overriding equals and hashCode will get you where you need to go. Beware, though, that this may have unintended side-effects with regard to Set and Map behavior, especially if your Notification objects don't have an ID until they are saved.

2.) How can I get a generic mock? e.g. whenever repository.save() is called, no matter the parameter, it always should perform a specific way

With Matchers, this is very simple:

when(notificationRepository.save(not)).thenReturn(any(Notification.class));

Though Matchers are very powerful, beware: they have some tricky rules associated with their use.

0
Dhaval On

For (1), as mentioned by Jeff, You might need to use eq() instead of direct reference to not1
For (2) You can use Mockito.any()
For e.g. when(notificationRepository.save(any(Notification.class))).thenReturn(not2);
This would create stubbing on mocked object notificationRepository which would always return not2 for any argument which is of type Notification. if save() method accepts Object then you can writewhen(notificationRepository.save(any(Object.class))).thenReturn(not2); which would return not2 for any argument of type Object