验证在子类上调用此方法时是否调用了重写的超类方法

时间:2014-03-13 14:52:03

标签: java inheritance junit mockito jmockit

我将使用此示例显示我的问题:
我有一个方法foo。该类有一个子类,它覆盖了这个方法 子类'方法调用超类'方法。我可以验证吗?
我不想测试超类中foo的作用。我只需要验证它是否被调用。 我知道重构可能会有所帮助(赞成组合而非继承等)但我无法做到这一点。
有没有办法达到我的需要?
以下是我尝试过的简单示例

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);
    }
}

这就是结果:

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>

显然这不是我想看到的。

我无法修改BaseClass。我不想测试BaseClass(我不负责)。我甚至不知道它到底是做什么的。我只需要验证它的方法是否被调用。其他不是我的问题。这是维持BaseClass的人的问题。

4 个答案:

答案 0 :(得分:7)

测试班级的行为,而不是实施

编写测试,以便调用该方法并期望执行某些操作。接下来检查对象现在代表您现在期望它代表的内容。

如果BaseClass.foo预计会将某个计数器增加100,但Subclass.foo会将某个计数器增加50,然后调用超类,验证该计数器现在是150而不是50。

不要看他们如何 - 他们可能会随着时间的推移而改变。测试行为。除了增加计数器之外,方法foo可以做其他事情 - 检查对象的状态而不是它的作用。

答案 1 :(得分:2)

可能的解决方案:使foo成为最终,并判断子类需要覆盖其他一些方法,即foo的实现,而不是实际的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);
    }

}

注意:这不是我的头脑,所以(1)我还没有测试过,(2)我不确定这是不是你真正想要的,(3)我&# 39;我不确定你的设计是否应该改进。这是关于&#34;如何确保覆盖方法调用超类方法&#34;的一般答案,而不是根据您的目的定制的答案。

答案 2 :(得分:1)

我毫不犹豫地给出了这个答案,因为这里的每个人(包括OP)都知道你可以这样做......但是为了回答OP的问题,你可以这样做:

而不是

@Override
public void reset() throws IOException{
    // ...

    super.reset();
}

这样做:

@Override
public void reset() throws IOException{
    // ...

    callSuperReset();
}

void callSuperReset() throws IOException {
    super.reset();
}

... verify callSuperReset确实被称为......

我是一个嘲笑的新手(毫无疑问它显示),我想很长一段时间命令只是&#34;你不能创造方法只是为了适合你的测试&#34;。

但在我的previous question中,davidxxx在他的回答中说

  

事实上,我想说的是:&#34;你不应该创造适合的方法   你的测试和打开应用程序的API是一个不受欢迎的   方式&#34;

鉴于此方法callSuperReset是包私有的,原则上是否存在任何问题? (除了它的总体不优雅)

答案 3 :(得分:0)

是的,可以编写这样的测试。如果您正在测试的是API的合同,而不是它的实现,那么想要这样做没有任何问题。

这是使用JMockit模拟API对问题中的人为示例进行测试:

@Test
public void subclassShouldObeyContractOfBaseClass(
    @Mocked final BaseClass anyBaseInstance)
{
    new ChildClass().foo(123);

    new Verifications() {{ anyBaseInstance.foo(anyInt); }};
}

如果有必要(由BaseClass#foo(int)的合同规定),测试还可以验证传递给基本方法的参数值为n + 1(或其他)。