Spring 中使用 Spock 的模拟注入不起作用
Mock injection in Spring using Spock not working
所以我在我的项目中使用 Spring=Retry
库并尝试 运行 一个测试用例。
我正在将服务 DCS 作为 return 一个新对象的 bean 加载。
它的两个依赖项 SSService 和 AttributeService 也作为 beans 加载。但这两个是嘲讽。
当我的测试规格 运行s 时,我可以在 DCS.execute 内部看到模拟就在那里。但是像 1 * SSService.read(_ as LComponent,_) >> mockSimpleSettingCommResult
这样的交互没有生效,导致空值而不是我想要的值 return.
@ContextConfiguration(classes = [SpringRetryConfig])
class DCSISpec extends Specification {
@Autowired
DCS DCS
@Autowired
SSService sSService
@Autowired
AttributeService attributeService
def setup() {
// DCS.SSService = SSService
// DCS.attributeService = attributeService
}
def "execute failure"(){
setup:
DataCollectionDataSet mockDataCollectionDataSet = Mock(DataCollectionDataSet)
LComponent mockLComponent = Mock(LComponent)
SSCommResult mockSimpleSettingCommResult = Mock(SSCommResult)
ReflectionTestUtils.setField(DCS, "SSService", SSService)
ReflectionTestUtils.setField(DCS, "attributeService", attributeService)
when:
DCS.execute(mockLComponent, mockDataCollectionDataSet)
then:
1 * mockSimpleSettingCommResult.getDegreeOfSuccess() >> SSCommResult.DegreeOfSuccess.FAILURE
1 * mockDataCollectionDataSet.getNamespace() >> DCSNamespace.xyz
1 * mockDataCollectionDataSet.getDataElements() >> ["FOO": "BAR"]
1 * SSService.read(_ as LComponent,_) >> mockSimpleSettingCommResult
3 * DCS.execute(_ as LComponent, _ as DataCollectionDataSet)
}
@Configuration
@EnableRetry
public static class SpringRetryConfig {
@Bean
public SSService SSService() {
Mockito.mock(SSService)
}
@Bean
public AttributeService attributeService() {
Mockito.mock(AttributeService)
}
@Bean
public DCS DCS() {
return new DCS();
}
}
}
这是我在尝试使用普通 Mockito 时遇到的异常,
java.lang.NullPointerException
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:41)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:136)
at com.lexmark.mps.cma.service.DataCollectionRetryTest.test_retry(DataCollectionRetryTest.groovy:70)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access[=12=]0(ParentRunner.java:50)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:222)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
鉴于我对上述问题的评论,我认为你最好在这里使用裸机 Mockito(w/o Spock 正如你在评论中所问的那样,因为我正在努力为如何制作可读的 Spock 规范这个用例):
编辑:完全正确的解决方案在这个下面
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DCSTest.SpringRetryConfig.class)
public class DCSTest {
@Autowired
private DCS dcs;
@Test
public void test_retry() {
//given:
LComponent component = mock(LComponent.class);
DataCollectionDataSet dataSet = mock(DataCollectionDataSet.class);
given(dcs.execute(component, dataSet)) //It's BDDMockito class
.willThrow(new RuntimeException("1"))
.willThrow(new RuntimeException("2"))
.willReturn("Foo");
//when:
String result = dcs.execute(component, dataSet);
//then:
verify(dcs, times(3)).execute(component, dataSet);
assertThat(result, equalTo("Foo"));
}
@Configuration
@EnableRetry
static class SpringRetryConfig {
@Bean
DCS dcs() {
return mock(DCS.class);
}
}
}
public class DCS {
@Retryable(maxAttempts = 3)
String execute(LComponent component, DataCollectionDataSet dataSet) {
return "Bar";
}
}
正确答案:
好的,作为 OP 并发现上面的 verify(dcs, times(3)).execute(component, dataSet);
没有按预期工作。无论您在 times()
中使用什么数字,测试都会成功。这是因为 @Retryable
围绕 dcs
模拟创建了一个方面。因此,对 dcs.execute
的每次调用都会被 Spring 拦截,并且 Mockito 不会实际验证调用。为了克服这个问题,我们可以围绕模拟创建我们自己的方面,作为副作用,计算 @Retryable
方法被调用了多少次。以下是此类解决方案的工作代码:
@RunWith(SpringJUnit4ClassRunner.class)
public class DCSTest {
@Autowired
private LComponent component;
@Autowired
private DataCollectionDataSet dataSet;
@Autowired
private DCS dcs;
@Autowired
private RetryCount retryCount;
@Test
public void test_retry() {
//when:
String result = dcs.execute(component, dataSet);
//then:
assertThat(retryCount.value, equalTo(3));
assertThat(result, equalTo("Foo"));
}
@Aspect
public static class RetryCount {
public int value = 0;
@Before("execution(* DCS.execute(..))")
public void advice() {
value++;
}
}
@Configuration
@EnableRetry
@EnableAspectJAutoProxy
public static class SpringRetryConfig {
@Bean
DCS dcs() {
DCS dcs = mock(DCS.class);
given(dcs.execute(component(), dataSet())) //It's BDDMockito class and take note that better to keep this declaration here so that Spring doesn't intercept the call once Retryable aspect is created
.willThrow(new RuntimeException("1"))
.willThrow(new RuntimeException("2"))
.willReturn("Foo");
return dcs;
}
@Bean
RetryCount retryCount() {
return new RetryCount();
}
@Bean
LComponent component() {
return new LComponent();
}
@Bean
DataCollectionDataSet dataSet() {
return new DataCollectionDataSet();
}
}
}
所以我在我的项目中使用 Spring=Retry
库并尝试 运行 一个测试用例。
我正在将服务 DCS 作为 return 一个新对象的 bean 加载。
它的两个依赖项 SSService 和 AttributeService 也作为 beans 加载。但这两个是嘲讽。
当我的测试规格 运行s 时,我可以在 DCS.execute 内部看到模拟就在那里。但是像 1 * SSService.read(_ as LComponent,_) >> mockSimpleSettingCommResult
这样的交互没有生效,导致空值而不是我想要的值 return.
@ContextConfiguration(classes = [SpringRetryConfig])
class DCSISpec extends Specification {
@Autowired
DCS DCS
@Autowired
SSService sSService
@Autowired
AttributeService attributeService
def setup() {
// DCS.SSService = SSService
// DCS.attributeService = attributeService
}
def "execute failure"(){
setup:
DataCollectionDataSet mockDataCollectionDataSet = Mock(DataCollectionDataSet)
LComponent mockLComponent = Mock(LComponent)
SSCommResult mockSimpleSettingCommResult = Mock(SSCommResult)
ReflectionTestUtils.setField(DCS, "SSService", SSService)
ReflectionTestUtils.setField(DCS, "attributeService", attributeService)
when:
DCS.execute(mockLComponent, mockDataCollectionDataSet)
then:
1 * mockSimpleSettingCommResult.getDegreeOfSuccess() >> SSCommResult.DegreeOfSuccess.FAILURE
1 * mockDataCollectionDataSet.getNamespace() >> DCSNamespace.xyz
1 * mockDataCollectionDataSet.getDataElements() >> ["FOO": "BAR"]
1 * SSService.read(_ as LComponent,_) >> mockSimpleSettingCommResult
3 * DCS.execute(_ as LComponent, _ as DataCollectionDataSet)
}
@Configuration
@EnableRetry
public static class SpringRetryConfig {
@Bean
public SSService SSService() {
Mockito.mock(SSService)
}
@Bean
public AttributeService attributeService() {
Mockito.mock(AttributeService)
}
@Bean
public DCS DCS() {
return new DCS();
}
}
}
这是我在尝试使用普通 Mockito 时遇到的异常,
java.lang.NullPointerException
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:41)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:136)
at com.lexmark.mps.cma.service.DataCollectionRetryTest.test_retry(DataCollectionRetryTest.groovy:70)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access[=12=]0(ParentRunner.java:50)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:222)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
鉴于我对上述问题的评论,我认为你最好在这里使用裸机 Mockito(w/o Spock 正如你在评论中所问的那样,因为我正在努力为如何制作可读的 Spock 规范这个用例):
编辑:完全正确的解决方案在这个下面
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DCSTest.SpringRetryConfig.class)
public class DCSTest {
@Autowired
private DCS dcs;
@Test
public void test_retry() {
//given:
LComponent component = mock(LComponent.class);
DataCollectionDataSet dataSet = mock(DataCollectionDataSet.class);
given(dcs.execute(component, dataSet)) //It's BDDMockito class
.willThrow(new RuntimeException("1"))
.willThrow(new RuntimeException("2"))
.willReturn("Foo");
//when:
String result = dcs.execute(component, dataSet);
//then:
verify(dcs, times(3)).execute(component, dataSet);
assertThat(result, equalTo("Foo"));
}
@Configuration
@EnableRetry
static class SpringRetryConfig {
@Bean
DCS dcs() {
return mock(DCS.class);
}
}
}
public class DCS {
@Retryable(maxAttempts = 3)
String execute(LComponent component, DataCollectionDataSet dataSet) {
return "Bar";
}
}
正确答案:
好的,作为 OP 并发现上面的 verify(dcs, times(3)).execute(component, dataSet);
没有按预期工作。无论您在 times()
中使用什么数字,测试都会成功。这是因为 @Retryable
围绕 dcs
模拟创建了一个方面。因此,对 dcs.execute
的每次调用都会被 Spring 拦截,并且 Mockito 不会实际验证调用。为了克服这个问题,我们可以围绕模拟创建我们自己的方面,作为副作用,计算 @Retryable
方法被调用了多少次。以下是此类解决方案的工作代码:
@RunWith(SpringJUnit4ClassRunner.class)
public class DCSTest {
@Autowired
private LComponent component;
@Autowired
private DataCollectionDataSet dataSet;
@Autowired
private DCS dcs;
@Autowired
private RetryCount retryCount;
@Test
public void test_retry() {
//when:
String result = dcs.execute(component, dataSet);
//then:
assertThat(retryCount.value, equalTo(3));
assertThat(result, equalTo("Foo"));
}
@Aspect
public static class RetryCount {
public int value = 0;
@Before("execution(* DCS.execute(..))")
public void advice() {
value++;
}
}
@Configuration
@EnableRetry
@EnableAspectJAutoProxy
public static class SpringRetryConfig {
@Bean
DCS dcs() {
DCS dcs = mock(DCS.class);
given(dcs.execute(component(), dataSet())) //It's BDDMockito class and take note that better to keep this declaration here so that Spring doesn't intercept the call once Retryable aspect is created
.willThrow(new RuntimeException("1"))
.willThrow(new RuntimeException("2"))
.willReturn("Foo");
return dcs;
}
@Bean
RetryCount retryCount() {
return new RetryCount();
}
@Bean
LComponent component() {
return new LComponent();
}
@Bean
DataCollectionDataSet dataSet() {
return new DataCollectionDataSet();
}
}
}