我从事Java项目,并开始使用Spock框架在Groovy中编写单元测试 . 但是我对Spock的 Mock 功能有一个问题,希望有人能弄清楚我做错了什么 .
我有三个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();
}
}
在这里可以看出 FooService
中的 doFoo
方法调用基类 HasFooContext
中的 getFoo
方法,后者又调用其 fooContext
属性的 getFoo
方法 . 它还声明从 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;
}
}
FooContext
中的 foo
属性不是final,因此 getFoo()
方法不应该是final,因此生成的代理应该没有问题拦截该方法(根据Java / Mockito测试) .
请注意,如果我用一个具体的 FooContext
替换模拟 FooContext
,如下所示:
class FooServiceGroovyUnitTest extends Specification {
...
def setup() {
// Spy on a real FooContext.
fooContext = Spy(FooContext)
fooService = new FooService()
fooService.fooContext = fooContext
}
}
然后Groovy / Spock单元测试通过 . 这表明在嘲笑具体类与侦察具体类时,行为是不同的 . Spock文档提到Mock api支持两个接口(使用动态代理)和类(使用CGLIB) .
据我所知,Java / Mockito单元测试和Groovy / Spock单元测试是等效的,但是在模拟 FooContext
类时我无法通过Groovy / Spock单元测试 .
对我做错了什么建议?
1 回答
相同交互的模拟和存根需要在同一个语句中进行:
大多数其他Java模拟框架(除了Mockito之外)都以相同的方式运行 . 有关此行为的更多详细信息,请参阅official documentation .