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

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


当前回答

您可以在测试中使用匿名类扩展抽象类。 例如(使用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方法。

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

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

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

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

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

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

下面的建议允许您在不创建“真正的”子类的情况下测试抽象类——Mock是子类,只是部分Mock。

使用Mockito.mock(My.class, Answers.CALLS_REAL_METHODS),然后模拟被调用的任何抽象方法。

例子:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}
 
public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Answers.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

注意:这个解决方案的美妙之处在于您不必实现抽象方法。CALLS_REAL_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()));
class Dependency{
  public void method(){};
}

public abstract class My {

  private Dependency dependency;
  public abstract boolean myAbstractMethod();

  public void myNonAbstractMethod() {
    // ...
    dependency.method();
  }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

  @InjectMocks
  private My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
  // we can mock dependencies also here
  @Mock
  private Dependency dependency;

  @Test
  private void shouldPass() {
    // can be mock the dependency object here.
    // It will be useful to test non abstract method
    my.myNonAbstractMethod();
  }
}