使用EasyMock可以创建实现多个接口的模拟对象吗?

53

使用EasyMock可以创建一个实现多个接口的模拟对象吗?

例如,实现接口 Foo 和接口 Closeable

在Rhino Mocks中,您可以在创建模拟对象时提供多个接口,但是EasyMock的createMock()方法只需要一个类型。

是否可以在EasyMock中实现这一点,而不必采用创建临时接口来扩展 FooCloseable 接口的回退方案,然后再对其进行模拟?

6个回答

91
尽管我基本上同意Nick Holt的回答,但我想指出,通过以下调用mockito可以实现您所要求的功能:
Foo mock = Mockito.mock(Foo.class, withSettings().extraInterfaces(Bar.class));

显然,当你需要将mock用作Bar时,你必须使用转换: (Bar)mock,但这个转换不会抛出ClassCastException

这里有一个更完整、虽然非常荒谬的例子:

import static org.junit.Assert.fail;
import org.junit.Test;
import static org.mockito.Mockito.*;
import org.mockito.Mockito;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import org.hamcrest.Matchers;

import java.util.Iterator;


public class NonsensicalTest {


    @Test
    public void testRunnableIterator() {
        // This test passes.

        final Runnable runnable = 
                    mock(Runnable.class, withSettings().extraInterfaces(Iterator.class));
        final Iterator iterator = (Iterator) runnable;
        when(iterator.next()).thenReturn("a", 2);
        doThrow(new IllegalStateException()).when(runnable).run();

        assertThat(iterator.next(), is(Matchers.<Object>equalTo("a")));

        try {
            runnable.run();
            fail();
        }
        catch (IllegalStateException e) {
        }
    }

15

你考虑过类似这样的东西吗:

interface Bar extends Foo, Closeable {
}

然后模拟接口 Bar?


1
是的,我只是想看看是否可以避免这种情况。根据我的问题:“是否可以使用EasyMock实现这一点,而不必采取创建一个临时接口来扩展Foo和Closeable,然后进行模拟的回退?” - Daniel Fortunov
我喜欢这个解决方案 - 在测试类中创建这样的接口对我来说没问题,好主意;-) - Betlista

10

EasyMock不支持这个功能,所以你只能使用临时接口的回退方法。

另外,我感觉有点代码异味-一个方法真的应该把一个对象视为两个不同的东西吗?在这种情况下是FooCloseable接口?

这让我觉得这个方法正在执行多个操作,虽然我猜想其中一个操作是“关闭”Closeable,但是是否需要“关闭”由调用代码决定不更好吗?

这样构造代码可以将“打开”和“关闭”保留在同一try ... finally块中,使代码更易读,而且这个方法更通用,可以传递实现Foo接口的对象。


4
我同意这个观点,但是为了进一步阐述:如果你正在使用依赖注入,并且你的类需要同时使用Foo和Closable,那么你应该为它们各自编写两个不同的设置方法。如果你选择为它们注入相同的对象,那很好,但是我认为被测试的类并不需要知道它们是同一个对象——应该将Foo视为Foo,将Closeable视为Closeable。 - matt b
Nick,Matt,感谢您们的参与。为了澄清情况,场景是Foo是一个模块化插件系统的接口。第三方模块实现Foo,然后由框架实例化并使用它们。它们也可以选择实现Closeable,如果这样做,那么当框架使用完它们时,将关闭它们。因此,单元测试需要覆盖两个不同的场景:一个同时也是Closeable的Foo和一个不是Closeable的Foo。我希望这讲得通。 - Daniel Fortunov
3
我不完全同意你的观点。考虑这种情况:当您有一个仅包含getter(例如getFirstName()getAddress()等)的接口Person,以及仅包含setter(例如setFirstName()setAddress()等)的接口ModifiablePerson时,您想要测试一个SUT,它需要Person,但会检查传递的对象是否为ModifiablePerson的实例,并根据此做一些操作。Closeable也是一个很好的例子:如果对象提供了“扩展”功能,并且通过instanceof进行了显式检查并得到利用,那么这有什么不好呢? - dma_k

7

一个基于Mockito,使用注释的最佳答案替代方案(原回答被点赞最多)。您可以从Mock注释直接设置extraInterfaces如下:

@RunWith(MockitoJUnitRunner.class)
public class MyTest {
    @Mock(extraInterfaces = Closeable.class)
    private Foo foo;
    ...
}

注意:extraInterfacesClass<?>[] 类型的,因此您可以根据需要指定多个接口。

如果您需要模拟额外接口的方法调用,则需要对模拟对象进行强制类型转换。例如,假设我想在调用模拟对象 fooclose() 方法时抛出 IOException,则相应的代码如下:

Mockito.doThrow(IOException.class).when((Closeable) foo).close();

我尝试了这个,但仍然出现了转换异常,我错过了什么吗? 我正在使用@RunWith(PowerMockRunner.class)运行我的测试用例。 - mksmanjit
@MANJITKUMAR 我刚刚测试了PowerMockito 2.0.2,它在我的端上运行正常。 - Nicolas Filotto
我正在使用1.5.5版本的PowerMock。 - mksmanjit

2
据我所知,目前仅有一款专门支持模拟多个接口的Java模拟工具,那就是JMockit。(该特性灵感来自于.NET工具Moq和Rhino Mocks。)
下面是一个示例(摘自mockit.ExpectationsUsingMockedTestJUnit 4 测试类):

@Test
public <M extends Dependency & Runnable> void mockParameterWithTwoInterfaces(final M mock)
{
   new Expectations()
   {
      {
         mock.doSomething(true); returns("");
         mock.run();
      }
   };

   assertEquals("", mock.doSomething(true));
   mock.run();
}

DependencyRunnable是接口。第一个接口有一个名为doSomething的方法,而第二个接口有一个名为run的方法。


这段代码似乎没有创建模拟对象。你会怎么做? - Thomas Dufour

2
解决这个问题的另一种方法是使用 CGLib mixin
final Interface1 interface1 = mockery.mock(Interface1.class);
final Interface2 interface2 = mockery.mock(Interface2.class);

service.setDependence(Mixin.create(new Object[]{ interface1, interface2 }));

mockery.checking(new Expectations(){{
    oneOf(interface1).doSomething();
    oneOf(interface2).doNothing();
}});

service.execute();

无论这是不是一个好主意,都需要讨论一下。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接