Java 使用 Mockito 测试抽象类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1087339/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 23:29:25  来源:igfitidea点击:

Using Mockito to test abstract classes

javaunit-testingmockingabstract-classmockito

提问by ripper234

I'd like to test an abstract class. Sure, I can manually write a mockthat inherits from the class.

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

Can I do this using a mocking framework (I'm using Mockito) instead of hand-crafting my mock? How?

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

采纳答案by Morten Lauritsen Khodabocus

The following suggestion let's you test abstract classes without creating a "real" subclass - the Mock isthe subclass.

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

use Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), then mock any abstract methods that are invoked.

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

Example:

例子:

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

Note: The beauty of this solution is that you do not haveto implement the abstract methods, as long as they are never invoked.

注意:此方法的好处是,你不具备实现的抽象方法,只要他们永远不会被调用。

In my honest opinion, this is neater than using a spy, since a spy requires an instance, which means you have to create an instantiatable subclass of your abstract class.

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

回答by Nick Holt

Assuming your test classes are in the same package (under a different source root) as your classes under test you can simply create the mock:

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

YourClass yourObject = mock(YourClass.class);

and call the methods you want to test just as you would any other method.

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

You need to provide expectations for each method that is called with the expectation on any concrete methods calling the super method - not sure how you'd do that with Mockito, but I believe it's possible with EasyMock.

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

All this is doing is creating a concrete instance of YouClassand saving you the effort of providing empty implementations of each abstract method.

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

As an aside, I often find it useful to implement the abstract class in my test, where it serves as an example implementation that I test via its public interface, although this does depend on the functionality provided by the abstract class.

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

回答by NamshubWriter

Mocking frameworks are designed to make it easier to mock out dependencies of the class you are testing. When you use a mocking framework to mock a class, most frameworks dynamically create a subclass, and replace the method implementation with code for detecting when a method is called and returning a fake value.

模拟框架旨在更轻松地模拟您正在测试的类的依赖项。当您使用模拟框架来模拟类时,大多数框架会动态创建一个子类,并将方法实现替换为用于检测方法何时被调用并返回假值的代码。

When testing an abstract class, you want to execute the non-abstract methods of the Subject Under Test (SUT), so a mocking framework isn't what you want.

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

Part of the confusion is that the answer to the question you linked to said to hand-craft a mock that extends from your abstract class. I wouldn't call such a class a mock. A mock is a class that is used as a replacement for a dependency, is programmed with expectations, and can be queried to see if those expectations are met.

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

Instead, I suggest defining a non-abstract subclass of your abstract class in your test. If that results in too much code, than that may be a sign that your class is difficult to extend.

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

An alternative solution would be to make your test case itself abstract, with an abstract method for creating the SUT (in other words, the test case would use the Template Methoddesign pattern).

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

回答by NamshubWriter

Try using a custom answer.

尝试使用自定义答案。

For example:

例如:

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

It will return the mock for abstract methods and will call the real method for concrete methods.

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

回答by Richard Nichols

You can achieve this by using a spy (use the latest version of Mockito 1.8+ though).

您可以通过使用间谍来实现这一点(尽管使用最新版本的 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()));

回答by Thomas Heiss

What really makes me feel bad about mocking abstract classes is the fact, that neither the default constructor YourAbstractClass() gets called (missing super() in mock) nor seems there to be any way in Mockito to default initialize mock properties (e.g List properties with empty ArrayList or LinkedList).

真正让我对模拟抽象类感到难过的事实是,默认构造函数 YourAbstractClass() 都没有被调用(在模拟中缺少 super()),而且 Mockito 中似乎没有任何方法来默认初始化模拟属性(例如列表属性)带有空的 ArrayList 或 LinkedList)。

My abstract class (basically the class source code gets generated) does NOT provide a dependency setter injection for list elements, nor a constructor where it initializes the list elements (which I tried to add manually).

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

Only the class attributes use default initialization: private List dep1 = new ArrayList; private List dep2 = new ArrayList

只有类属性使用默认初始化:private List dep1 = new ArrayList; 私有列表 dep2 = 新的 ArrayList

So there is NO way to mock an abstract class without using a real object implementation (e.g inner class definition in unit test class, overriding abstract methods) and spying the real object (which does proper field initialization).

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

Too bad that only PowerMock would help here further.

太糟糕了,只有 PowerMock 可以进一步帮助。

回答by David Moles

If you just need to test some of the concrete methods without touching any of the abstracts, you can use CALLS_REAL_METHODS(see Morten's answer), but if the concrete method under test calls some of the abstracts, or unimplemented interface methods, this won't work -- Mockito will complain "Cannot call real method on java interface."

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

(Yes, it's a lousy design, but some frameworks, e.g. Tapestry 4, kind of force it on you.)

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

The workaround is to reverse this approach -- use the ordinary mock behavior (i.e., everything's mocked/stubbed) and use doCallRealMethod()to explicitly call out the concrete method under test. E.g.

解决方法是颠倒这种方法——使用普通的模拟行为(即,一切都被模拟/存根)并使用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();
    }
}


Updated to add:

更新添加:

For non-void methods, you'll need to use thenCallRealMethod()instead, e.g.:

对于非 void 方法,您需要thenCallRealMethod()改用,例如:

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

Otherwise Mockito will complain "Unfinished stubbing detected."

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

回答by DwB

You can extend the abstract class with an anonymous class in your test. For example (using Junit 4):

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

private AbstractClassName classToTest;

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

// Test the AbstractClassName methods.

回答by Samuel

You can instantiate an anonymous class, inject your mocks and then test that class.

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

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

Keep in mind that the visibility must be protectedfor the property myDependencyServiceof the abstract class ClassUnderTest.

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

回答by Smart Coder

PowerMock's Whitebox.invokeMethod(..)can be handy in this case.

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