Published
- 2 min read
Mockito ArgumentCaptor with inheritance
Working with Mockito’s ArgumentCaptor I discover it has a awful issue with inheritance.
Let’s suppose we have a parent class named Animal
and two child classes Dog
and Cat
:
public class Animal {
private String species;
public Animal(String species) {
this.species = species;
}
public String getSpecies() {
return species;
}
}
public class Cat extends Animal {
private final String colorEyes;
public Cat(String colorEyes) {
super("Cat");
this.colorEyes = colorEyes;
}
}
public class Dog extends Animal {
private String name;
public Dog(String name) {
super("Dog");
this.name = name;
}
public String getName() {
return name;
}
}
And a class that accept an argument of type Animal
:
public class AnimalProcessor {
public void processAnimal(Animal animal) {
System.out.println(animal.getSpecies());
}
}
One might think on writing the unit test of AnimalProcessor
similar to this snippet:
public class ArgumentCaptorInheritanceTest {
@Mock
private AnimalProcessor animalProcessor;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldProcessDog() {
Dog dog = new Dog("Rex");
Cat cat = new Cat("blue");
ArgumentCaptor<Dog> dogArgumentCaptor = ArgumentCaptor.forClass(Dog.class);
animalProcessor.processAnimal(dog);
animalProcessor.processAnimal(cat);
Mockito.verify(animalProcessor).processAnimal(dogArgumentCaptor.capture());
Assert.assertEquals("Rex", dogArgumentCaptor.getValue().getName());
}
Well, this fails … ArgumentCaptor
does not work well in this case. It makes the verify
fail because the method has been called twice.
The expected behaviour is that only verify
analyses the calls when the Dog
instance is passed.
In order to execute the test in this way, some ugly workaround needs to be done:
@Test
public void shouldProcessDog() {
Dog dog = new Dog("Rex");
Cat cat = new Cat("blue");
ArgumentCaptor<Animal> animalCaptor = ArgumentCaptor.forClass(Animal.class);
animalProcessor.processAnimal(dog);
animalProcessor.processAnimal(cat);
Mockito.verify(animalProcessor, Mockito.times(2)).processAnimal(animalCaptor.capture());
List<Animal> processedAnimals = animalCaptor.getAllValues();
Optional<Animal> dogOptional = processedAnimals.stream()
.filter(a -> a instanceof Dog)
.findFirst();
Assert.assertTrue(dogOptional.isPresent());
Assert.assertEquals("Rex", ((Dog) dogOptional.get()).getName());
}
Instead of capturing the arguments for Dog
, you can do it for Animal
.
Then the verify
will successfully capture the two calls and then all the captured values can be analysed. You can filter the captured values for the objects that are of the interested instance.
This example comes from:
https://github.com/adriangalera/java-sandbox/tree/master../../test/java/mockito/argcaptor
This is a known issue (already reported as an issue in their repo):
https://github.com/mockito/mockito/issues/565
As of the day of writing the article, the issues is still there and it has been opened from 2016 …