I'll show my problem using this example:
I have a class with a method foo
. That class has a subclass which overrides this method.
Subclass' method calls superclass' method. Can I verify that?
I don't want to test what foo
in superclass does. I just need to verify that it was called.
I know that refactoring could help (favour composition over inheritance, etc) but I am unable to do that.
Is there any way to achieve what I need?
Below is simple example of what I've tried
package tmp;
import org.junit.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.times;
public class Example {
@Test
public void test() {
// given
ChildClass childClass = new ChildClass();
ChildClass spyChildClass = Mockito.spy(childClass);
// when
spyChildClass.foo(100);
// then
Mockito.verify((BaseClass) spyChildClass, times(1)).foo(101);
}
}
abstract class BaseClass {
public void foo(int n) {
System.out.printf("BaseClass.foo(%d)%n", n);
}
}
class ChildClass extends BaseClass {
@Override
public void foo(int n) {
System.out.printf("ChildClass.foo(%d)%n", n);
super.foo(n + 1);
}
}
And this is the result:
ChildClass.foo(100)
BaseClass.foo(101)
Argument(s) are different! Wanted:
childClass.foo(101);
-> at tmp.Example.test(Example.java:19)
Actual invocation has different arguments:
childClass.foo(100);
-> at tmp.Example.test(Example.java:16)
Expected :childClass.foo(101);
Actual :childClass.foo(100);
<Click to see difference>
Obviously it's not what I wanted to see.
I can't modify BaseClass
. I don't want to test BaseClass
(I am not responsible for it). I don't even neet to know what exactly it does. I just need to verify that its method was called. Wnything else is not my problem. Its the problem of people who maintain BaseClass
.
Test the behaviour of the class, not it's implementation.
Write the test such that the method is called and is expected to do something. Next check the object now represents what you now expect it to represent.
If BaseClass.foo
is expected to increment some counter by 100, yet Subclass.foo
increments some counter by 50 then calls the superclass, verify that the counter is now 150 and not just 50.
Don't peek the how - they may change over time. Do test the behaviour. The method foo
may do other things besides increase counters - check the state of the object not what it did.
Possible solution: Make foo
final, and decree that subclasses need to override some other method, the implementation of foo
, instead of the actual foo
.
abstract class BaseClass {
private boolean myFooCalled;
public final void foo(int n) {
myFooCalled = false;
fooImpl(int n);
if (!myFooCalled) { ... }
}
public void fooImpl(int n) {
myFooCalled = true;
System.out.printf("BaseClass.foo(%d)%n", n);
}
}
Notes: This is off the top of my head, so (1) I haven't tested it, (2) I'm not sure if this is what you really want, (3) I'm not sure whether your design ought to be improved. This is a general answer about "how you could make sure an overriding method calls the superclass method", not an answer tailored to your purposes.
I hesitate to give this answer because everyone here (including the OP) knows you can do this... but to answer the OP's question you can do this:
Instead of having
@Override
public void reset() throws IOException{
// ...
super.reset();
}
do this:
@Override
public void reset() throws IOException{
// ...
callSuperReset();
}
void callSuperReset() throws IOException {
super.reset();
}
... and verify
that callSuperReset
was indeed called...
I am a mocking newb (no doubt it shows), and I thought for a long time the command was simply "Thou shalt not create methods just to suit your tests".
But in a previous question of mine, davidxxx in his answer says
In fact I would say rather : "thou shalt not create methods to suit your tests and that open the API of the application in an undesirable way"
Given that this method callSuperReset
is package-private, is there any problem in principle with it? (other than its total inelegance)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With