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

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


当前回答

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

private AbstractClassName classToTest;

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

// Test the AbstractClassName methods.

其他回答

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

private AbstractClassName classToTest;

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

// Test the AbstractClassName methods.
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();
  }
}

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将报怨“检测到未完成的存根”。

下面的建议允许您在不创建“真正的”子类的情况下测试抽象类——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导致所有真实方法按原样运行,只要您不在测试中存根它们。

在我看来,这比使用间谍更简洁,因为间谍需要一个实例,这意味着您必须为抽象类创建一个可实例化的子类。