我从事Java项目,并已开始使用Spock框架在Groovy中编写我的单元测试。但是我对Spock的模拟功能有疑问,希望有人能弄清楚我在做什么错。

我有三个Java类:FooContext(包含foo属性),HasFooContext类(包含fooContext属性)和从FooService继承的HasFooContext(并具有调用fooContext的操作):

public class FooContext {
  private Object foo = new Object();
  public Object getFoo() {
    return foo;
  }
}

public abstract class HasFooContext {
  private FooContext fooContext;
  public void setFooContext(FooContext fooContext) {
    this.fooContext = fooContext;
  }
  public Object getFoo() {
    Object foo = fooContext.getFoo();
    assert foo != null : "no foo available";
    return foo;
  }
}

public class FooService extends HasFooContext {
  public void doFoo() {
    getFoo();
  }
}

在这里可以看到doFoo中的FooService方法调用基类getFoo中的HasFooContext方法,该类又调用了其getFoo属性的fooContext方法。它还断言从fooContext.getFoo()返回的值不为null。

我使用Mockito在Java中编写了以下单元测试,以验证调用doFoo将调用fooContext.getFoo()方法:
public class FooServiceJavaUnitTest {

  private FooContext fooContext;
  private FooService fooService;

  @Before
  public void setup() {
    Object foo = new Object();

    fooContext = mock(FooContext.class);
    when(fooContext.getFoo()).thenReturn(foo);

    fooService = new FooService();
    fooService.setFooContext(fooContext);
  }

  @Test
  public void doFooInvokesGetFoo() {
    fooService.doFoo();
    verify(fooContext, times(1)).getFoo();
  }

}

此测试按预期成功运行。

然后,我使用Spock在Groovy中编写了以下单元测试:
class FooServiceGroovyUnitTest extends Specification {

  private FooContext fooContext;
  private FooService fooService;

  def setup() {
    // Create a mock FooContext.
    fooContext = Mock(FooContext)
    fooContext.getFoo() >> new Object()

    fooService = new FooService()
    fooService.fooContext = fooContext
  }

  def "doFoo invokes getFoo"() {
    when: "call doFoo"
    fooService.doFoo()

    then: "getFoo is invoked"
    1 * fooContext.getFoo()
  }

}

该测试失败,如下所示:

FooServiceGroovyUnitTest.doFoo调用getFoo:21没有可用的foo

也就是说,使用Groovy / Spock时以下失败,但使用Java / Mockito时失败:
public abstract class HasFooContext {
  ...
  public Object getFoo() {
    Object foo = fooContext.getFoo();
    assert foo != null : "no foo available";
    return foo;
  }
}
foo中的FooContext属性不是最终的,因此getFoo()方法不应该是最终的,因此生成的代理在侦听该方法时应该没有问题(根据Java / Mockito测试)。

请注意,如果我将 spy FooContext替换为监视具体的FooContext,如下所示:
class FooServiceGroovyUnitTest extends Specification {
  ...
  def setup() {
    // Spy on a real FooContext.
    fooContext = Spy(FooContext)

    fooService = new FooService()
    fooService.fooContext = fooContext
  }
}

然后Groovy / Spock单元测试通过。这表明在 mock 具体类与侦查具体类时的行为是不同的。 Spock文档提到Mock api支持接口(interface)(使用动态代理)和类(使用CGLIB)。

据我所知,Java / Mockito单元测试和Groovy / Spock单元测试是等效的,但是在模拟FooContext类时,我无法通过Groovy / Spock单元测试。

关于我在做什么错的任何建议?

最佳答案

相同交互的 mock 和存根需要在同一条语句中发生:

1 * fooContext.getFoo() >> new Object()

大多数其他Java模拟框架(Mockito除外)的行为均相同。有关此行为的更多详细信息,请查询official documentation

关于unit-testing - Spock没有正确 mock 具体的类?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22210943/

10-11 03:59