我想测试一个抽象类。当然,我可以手动编写一个从该类继承的模拟。

我可以使用mock框架(我使用Mockito)而不是手工制作我的mock吗?如何?


当前回答

真正让我对模拟抽象类感到糟糕的是,既没有调用默认构造函数YourAbstractClass()(在mock中缺少super()),也似乎没有任何方法在Mockito中默认初始化模拟属性(例如List属性与空ArrayList或LinkedList)。

我的抽象类(基本上生成了类源代码)没有为列表元素提供依赖setter注入,也没有初始化列表元素的构造函数(我试图手动添加)。

只有类属性使用默认初始化:

private List<MyGenType> dep1 = new ArrayList<MyGenType>();
private List<MyGenType> dep2 = new ArrayList<MyGenType>();

因此,如果不使用真正的对象实现(例如单元测试类中的内部类定义,覆盖抽象方法)和监视真正的对象(它会进行适当的字段初始化),就无法模拟抽象类。

遗憾的是,只有PowerMock才能进一步提供帮助。

其他回答

您可以在测试中使用匿名类扩展抽象类。 例如(使用Junit 4):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

尝试使用自定义答案。

例如:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

它将为抽象方法返回mock,为具体方法调用real方法。

Mockito允许通过@Mock注释来模拟抽象类:

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

缺点是如果需要构造函数参数,则不能使用它。

PowerMock的Whitebox.invokeMethod(..)在这种情况下很方便。

如果你只是需要测试一些具体方法而不涉及任何抽象,你可以使用CALLS_REAL_METHODS(见Morten的回答),但是如果被测试的具体方法调用一些抽象,或未实现的接口方法,这将不起作用——Mockito将抱怨“不能在java接口上调用真实方法”。

(是的,这是一个糟糕的设计,但有些框架,例如Tapestry 4,会强迫你这样做。)

解决方法是反转这种方法——使用普通的模拟行为(即,所有东西都被模拟/存根)并使用doCallRealMethod()显式地调用测试中的具体方法。如。

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

更新如下:

对于非void方法,你需要使用encallrealmethod()来代替,例如:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

否则Mockito将报怨“检测到未完成的存根”。