Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verify that overriden superclass method is called when invoking this method on subclass

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.

like image 810
Pawel P. Avatar asked Mar 13 '14 14:03

Pawel P.


3 Answers

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.

like image 158
jmkgreen Avatar answered Sep 27 '22 20:09

jmkgreen


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.

like image 29
ajb Avatar answered Sep 27 '22 20:09

ajb


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)

like image 34
mike rodent Avatar answered Sep 27 '22 20:09

mike rodent