So I am using the Spring=Retry
library in my project and trying to run a test case for that.
I am loading the service DCS as a bean that returns a new object.
Its two dependencies SSService and AttributeService are loaded as beans as well. But these two are mocks.
When my test spec runs i can see inside DCS.execute that the mocks are there alright. But the interactions on them like 1 * SSService.read(_ as LComponent,_) >> mockSimpleSettingCommResult
is not taking effect resulting in a null value instead of the value i want it to 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();
}
}
}
This is the exception i get when trying to use plain 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$1.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$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.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)
Given my comments to the question above, I think you are better off using bare Mockito here (w/o Spock as you asked in the comments as I'm struggling on how to make a readable Spock specification for this use case):
EDIT: The completely correct solution is below this one
@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";
}
}
CORRECT SOLUTION:
Ok, as the OP and discovered the above verify(dcs, times(3)).execute(component, dataSet);
doesn't work as expected. No matter what number you use in times()
the test will always succeeds. This is because @Retryable
creates an aspect around the dcs
mock. As a result, each call to dcs.execute
is intercepted by Spring and Mockito doesn't actually verify the call. To overcome this, we can create our own aspect around the mock and just as a side effect, count how many times the @Retryable
method is called. Below is the working code of such a solution:
@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();
}
}
}