【问题标题】:Mock injection in Spring using Spock not working使用Spock在Spring中模拟注入不起作用
【发布时间】:2019-02-12 09:59:09
【问题描述】:

所以我在我的项目中使用Spring=Retry 库并尝试为此运行一个测试用例。 我将服务 DCS 作为返回新对象的 bean 加载。 它的两个依赖项 SSService 和 AttributeService 也作为 bean 加载。但这两个是嘲笑。 当我的测试规范运行时,我可以在 DCS.execute 中看到模拟正常。但是像1 * SSService.read(_ as LComponent,_) >> mockSimpleSettingCommResult 这样的交互没有生效,导致空值而不是我希望它返回的值。

@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$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)

【问题讨论】:

  • 你为什么要在 Mockitos 模拟 SSService 上配置 Spock 交互。它不是那样工作的。此外,你在那次互动中拼错了sSSservice(你有SSSservice
  • 最后,您无法通过在真实对象(3 * DCS.execute...)上配置交互来测试重试。只需模拟它,以便第一次调用导致重试并且最终调用成功。然后查看调用次数
  • @DmitryKhamitov 你能告诉我一个使用 spock 的春季重试测试的例子吗?我很难通过 Spock 进行重试测试。最重要的是,我们的项目使用了相当老的 Spock 0.7。
  • 我知道已经有一个公认的答案,但问题是关于 Spock,所以我想知道如果你可以使用 Spock 模拟,你为什么要使用 Mockito。如果这是一个选项,您会对 Spock 解决方案感兴趣,还是只是从 Spock 迁移到 JUnit?
  • 我明白你的意思。我还没有找到使用 Spock 的更好方法。但是,如果我能找到一个,我会自己添加一个新答案。现在我正在使用 JUNit4 和 Mockito 来做到这一点。是的,如果有 Spock 选项,我会接受!

标签: spring mocking spock spring-retry


【解决方案1】:

鉴于我对上述问题的 cmets,我认为您最好在这里使用裸 Mockito(没有 Spock,正如您在 cmets 中所问的那样,因为我正在努力为这个用例制定可读的 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();
        }
    }
}

【讨论】:

  • 所以我使用 LComponent 和 DataCollectionDataSet 的 Mock 对象(使用 Mockito)来执行此操作,并且我不断收到方法调用异常。
  • 我已经编辑了我的答案,所以它使用Mockito.mock 来表示LComponentDataCollectionDataSet。这个对我有用。您是否尝试过相同的设置?你能提供你在错误中看到的堆栈跟踪的 sn-p 吗?调用异常仅向下 2-3 行,包括。会好的
  • 我在问题的最后部分更新了跟踪。请注意,当我从调试器中运行时,我在given 的行中得到它。
  • hm,您运行的代码与答案中的完全相同吗?因为,我尝试了不同的情况,但无法实现相同的堆栈跟踪
  • 是的,我正在运行同样的事情。我有 JUnit 4.12、Mockito 1.10、Spring boot 1.2 和 spring 4.3.x
猜你喜欢
  • 1970-01-01
  • 2016-12-11
  • 1970-01-01
  • 2017-05-25
  • 1970-01-01
  • 2016-08-20
  • 1970-01-01
  • 1970-01-01
  • 2016-04-17
相关资源
最近更新 更多