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

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


当前回答

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

其他回答

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

private AbstractClassName classToTest;

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

// Test the AbstractClassName methods.

您可以通过使用间谍来实现这一点(尽管使用Mockito 1.8+的最新版本)。

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

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();
        // ...
    }
}

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

模拟框架的设计目的是为了更容易模拟出正在测试的类的依赖关系。当您使用模拟框架模拟一个类时,大多数框架都会动态地创建一个子类,并将方法实现替换为用于检测方法何时被调用并返回假值的代码。

在测试抽象类时,您希望执行Subject Under Test (SUT)的非抽象方法,因此模拟框架并不是您想要的。

造成这种困惑的部分原因是,您所链接的问题的答案要求手工创建一个从抽象类扩展而来的模拟。我不认为这样的课程是mock。mock是一个类,它被用作依赖项的替代品,它是按照期望进行编程的,并且可以查询这些期望是否得到满足。

相反,我建议在测试中定义抽象类的非抽象子类。如果这导致了太多的代码,那么这可能表明您的类难以扩展。

另一种解决方案是使您的测试用例本身是抽象的,使用抽象的方法来创建SUT(换句话说,测试用例将使用模板方法设计模式)。

您可以实例化一个匿名类,注入您的模拟,然后测试该类。

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

请记住,必须保护抽象类ClassUnderTest的属性myDependencyService的可见性。