238

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

我可以使用模拟框架(我正在使用 Mockito)而不是手工制作我的模拟来做到这一点吗?如何?

4

12 回答 12

353

以下建议让您在不创建“真实”子类的情况下测试抽象类 - Mock子类。

使用Mockito.mock(My.class, Mockito.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, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

注意:这个解决方案的美妙之处在于您不必实现抽象方法,只要它们从未被调用过。

老实说,这比使用 spy 更简洁,因为 spy 需要一个实例,这意味着您必须创建抽象类的可实例化子类。

于 2010-11-30T19:25:00.590 回答
74

如果您只需要测试一些具体方法而不涉及任何抽象,则可以使用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 方法,您需要thenCallRealMethod()改用,例如:

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

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

于 2011-09-12T23:14:51.250 回答
20

您可以通过使用间谍来实现这一点(尽管使用最新版本的 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()));
于 2009-07-28T04:14:26.033 回答
16

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

在测试抽象类时,您希望执行被测对象 (SUT) 的非抽象方法,因此您不需要模拟框架。

部分困惑是,您链接到的问题的答案是手工制作一个从您的抽象类扩展的模拟。我不会把这样的课程称为模拟。模拟是用作替代依赖项的类,根据期望进行编程,并且可以查询以查看是否满足这些期望。

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

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

于 2009-07-06T15:06:19.783 回答
8

尝试使用自定义答案。

例如:

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

它将返回抽象方法的模拟,并将调用具体方法的真实方法。

于 2009-07-15T20:38:54.840 回答
5

真正让我对模拟抽象类感到难过的是这样一个事实,即既没有YourAbstractClass()调用默认构造函数(在模拟中丢失super()),也没有在 Mockito 中默认初始化模拟属性(例如List带有空ArrayList或的属性LinkedList)的任何方式。

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

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

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

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

太糟糕了,只有PowerMock才能进一步提供帮助。

于 2010-11-02T18:30:15.507 回答
3

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

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

于 2018-08-15T22:19:46.237 回答
2

假设您的测试类与您的测试类在同一个包中(在不同的源根目录下),您可以简单地创建模拟:

YourClass yourObject = mock(YourClass.class);

并像调用任何其他方法一样调用要测试的方法。

您需要为调用的每个方法提供期望,并期望调用超级方法的任何具体方法 - 不确定您如何使用 Mockito 做到这一点,但我相信使用 EasyMock 是可能的。

所有这一切都是创建一个具体的实例,YouClass并为您节省提供每个抽象方法的空实现的工作。

顺便说一句,我经常发现在我的测试中实现抽象类很有用,它作为我通过其公共接口测试的示例实现,尽管这确实取决于抽象类提供的功能。

于 2009-07-06T14:53:59.803 回答
2

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

private AbstractClassName classToTest;

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

// Test the AbstractClassName methods.
于 2015-03-13T19:43:07.450 回答
1
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();
  }
}
于 2020-10-21T15:30:43.633 回答
0

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

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

请记住,可见性必须针对抽象类protected的属性。myDependencyServiceClassUnderTest

于 2017-07-27T11:27:19.397 回答
0

在这种情况下, PowerMockWhitebox.invokeMethod(..)可以派上用场。

于 2018-07-27T13:50:16.340 回答