Cruces
Cruces

Reputation: 3119

Checking if method was invoked with specific arguements only once with mockito

I'm using Mockito in kotlin to check that a list is paged correctly

I use this code

    logic.searchItems(filter)
    verify(vm).setItems(all.subList(0, 10), true)
    logic.loadNext()
    verify(vm).setItems(all.subList(0, 20), true)     (1)
    logic.loadNext()
    verify(vm).setItems(all.subList(0, 30), true)     (2)

In theory this should work, but I get too many invocations exception in (1) and (2).

If I use times(1) in (1) and times(2) in (2) the test passes. But I wish to verify that that method is called with those specific arguments.

Can this be done with Mockito?

Upvotes: 3

Views: 103

Answers (2)

Cruces
Cruces

Reputation: 3119

After some testing I figured out that the problem was that in my logic I added the results to the same list before sending them back like so:

addItems(results:List<Item>()){
    //verifications here
    myItems.addAll(results)
    vm.setItems(myItems,true)
}

this for some reason made mockito think it was the same invocation?

when I do it like this it works

addItems(results:List<Item>()){
    //verifications here
    myItems.addAll(results)
    vm.setItems(myItems.map { it.copy() },true)
}

I do not know if it is a bug, or the intended behaviour, but at least this works

edit:

Ok so I feel like an idiot because it is not a bug at all, it is indeed the intended behaviour, and it saved me some future trouble

when using captors, I figured out that when the setItems method is called Mockito keeps a reference to the returned list.

the next time it is called I used to add items to myItems and Mockito kept the new reference

but since I performed an addAll operation the reference that was previously kept also got updated, so it is indeed normal that Mockito needs the times(2) method when I call it because the list it got in the first invocation was updated and will match the second list.

the correct way it seems, is to send a copy of that list, that way the viewModel can't alter the original list in any way

Upvotes: 1

Maciej Kowalski
Maciej Kowalski

Reputation: 26502

With this kind of parameters, being a Collection with specific content I would advise using the ArgumentCaptor feature. Thanks to that you will be able to capture the passed parameters and later assert their value / state using tools like Hamcrest or AssertJ:

final ArgumentCaptor<List> captorListOne = ArgumentCaptor.forClass(List.class);
final ArgumentCaptor<List> captorListTwo = ArgumentCaptor.forClass(List.class);
final ArgumentCaptor<List> captorListThree = ArgumentCaptor.forClass(List.class);

verify(vm).setItems(captorListOne.capture(), true)
logic.loadNext()
verify(vm).setItems(captorListTwo.capture(), true)     
logic.loadNext()
verify(vm).setItems(captorListThree.capture(), true) 

List listToAssert = captorListOne.getValue();
...

More the ArgumentCaptor

Upvotes: 0

Related Questions