模拟或存根以进行链式通话


76
protected int parseExpire(CacheContext ctx) throws AttributeDefineException {
    Method targetMethod = ctx.getTargetMethod();
    CacheEnable cacheEnable = targetMethod.getAnnotation(CacheEnable.class);
    ExpireExpr cacheExpire = targetMethod.getAnnotation(ExpireExpr.class);
    // check for duplicate setting
    if (cacheEnable.expire() != CacheAttribute.DO_NOT_EXPIRE && cacheExpire != null) {
        throw new AttributeDefineException("expire are defined both in @CacheEnable and @ExpireExpr");
    }
    // expire time defined in @CacheEnable or @ExpireExpr
    return cacheEnable.expire() != CacheAttribute.DO_NOT_EXPIRE ? cacheEnable.expire() : parseExpireExpr(cacheExpire, ctx.getArgument());
}

那是测试的方法

Method targetMethod = ctx.getTargetMethod();
CacheEnable cacheEnable = targetMethod.getAnnotation(CacheEnable.class);

我必须模拟三个CacheContext,Method和CacheEnable。有什么想法可以使测试用例更加简单吗?

Answers:


160

Mockito可以处理链接的存根

Foo mock = mock(Foo.class, RETURNS_DEEP_STUBS);

// note that we're stubbing a chain of methods here: getBar().getName()
when(mock.getBar().getName()).thenReturn("deep");

// note that we're chaining method calls: getBar().getName()
assertEquals("deep", mock.getBar().getName());

AFAIK,链中的第一个方法返回一个模拟,该模拟被设置为在第二个链式方法调用中返回您的值。

Mockito的作者指出,这仅应用于旧代码。否则,更好的方法是将行为推送到CacheContext并提供完成工作本身所需的任何信息。您从CacheContext中获取的信息量表明您的类具有令人羡慕的功能


恩,Szczepan创建了Mockito,因为他看到我和其他一些人手动开发了我们自己的模拟,而不是使用EasyMock,并决定模拟应该对BDD更好用-所以显然我更喜欢Mockito!但是他分叉EasyMock来做到这一点,因此,是的,EasyMock很棒。我们站在巨人的肩膀上...
2011年

这很有趣,我赞成。但是您不能简单地使用Foo foo=mock(Foo.class); Bar bar=mock(Bar.class); when(foo.getBar()).thenReturn(bar); when(bar.getName()).thenReturn("deep")'。在我看来,这很容易阅读,不需要理解“ DEEP”桩的概念。(顺便说一句,我喜欢Mockito。)
cdunn2001

1
如果其中一个链返回通用类型,则此方法不起作用。还有其他人面对这个问题吗?
Vivek Kothari 2015年

4
@Magnilex实际上官方资源中。如上所示,源代码。“请注意,在大多数情况下,模拟返回模拟是错误的。” 另外:警告:常规清洁代码很少需要使用此功能!留给旧代码。模拟一个模拟返回一个模拟,返回一个模拟,(...),返回一些有意义的暗示,以违反Demeter法则或模拟一个值对象(众所周知的反模式)。 github.com/mockito/mockito/blob/master/src/main/java/org/…(大部分在1393行)。
Lunivore '17

1
@RuifengMa我从本文档中猜测是@Mock(answer = RETURNS_DEEP_STUBS)-试试吧,让我们知道! static.javadoc.io/org.mockito/mockito-core/2.2.28/org/mockito/…–
Lunivore

5

以防万一您正在使用Kotlin。MockK不说有关链接是一个不好的做法,任何东西,轻松地让你做这个

val car = mockk<Car>()

every { car.door(DoorType.FRONT_LEFT).windowState() } returns WindowState.UP

car.door(DoorType.FRONT_LEFT) // returns chained mock for Door
car.door(DoorType.FRONT_LEFT).windowState() // returns WindowState.UP

verify { car.door(DoorType.FRONT_LEFT).windowState() }

confirmVerified(car)

是的,很棒的事情,确实有助于减少样板代码的数量。
AbstractVoid19

3

我的建议是使您的测试用例更简单,是重构您的方法。

每当我发现自己无法测试一种方法时,这对我来说都是一种代码味道,我问为什么很难进行测试。如果代码难以测试,则可能很难使用和维护。

在这种情况下,这是因为您的方法链深入了多个层次。也许传入ctx,cacheEnable和cacheExpire作为参数。


是的,但是这些字段是在运行时来自aop上下文的,很难简化环境。
jilen 2011年

JMockit中有一些技术可以做到这一点。您可以将字段模拟到对象中,同时模拟AOP字段注入。或者,您可以使用解封装技术使用模拟实例初始化证明字段
Konstantin Pribluda 2011年

你能举个例子@doug吗?
ScottyBlades

1

我发现JMockit更易于使用,并且可以完全切换到它。查看使用它的测试用例:

https://github.com/ko5tik/andject/blob/master/src/test/java/de/pribluda/android/andject/ViewInjectionTest.java

在这里,我模拟了Activity基类,该基类来自Android SKD,并且完全存根。使用JMockit,您可以嘲笑最终的,私有的,抽象的或其他任何东西。

在您的测试用例中,它看起来像:

public void testFoo(@Mocked final Method targetMethod, 
                    @Mocked  final CacheContext context,
                    @Mocked final  CacheExpire ce) {
    new Expectations() {
       {
           // specify expected sequence of infocations here

           context.getTargetMethod(); returns(method);
       }
    };

    // call your method
    assertSomething(objectUndertest.cacheExpire(context))

1
认为您的答案,但我以任何方式使用了模仿。
吉伦2011年

1
请注意,JMockit具有专门用于链接调用的注释:@Cascading。同样,在这种情况下,您可能希望使用NonStrictExpectations代替Expectations,假设不需要验证对模拟方法的调用。
罗杰里奥

谢谢,我错过了这个批注;)简化了单元测试
Konstantin Pribluda 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.