【问题标题】:How to create Spock mocks outside of a specification class?如何在规范类之外创建 Spock 模拟?
【发布时间】:2013-05-29 09:48:07
【问题描述】:

我们将 Spock 测试与 Spring 的 @ContextConfiguration 结合起来,因此我们可以在 Spring 上下文中构建 bean,然后使用 Spock 进行实际测试。我们想将 spock 模拟注入到我们的 spring bean 中。对于 Mockito,有一个扩展程序允许您执行以下操作:

 <mockito:mock id="accountService" class="org.kubek2k.account.DefaultAccountService" />

然后将此模拟引用到其他 spring bean。 Spock 似乎没有这样的扩展。如果您知道如何在 Specification 类之外创建 Mocks,那么再次构建它可能不会花费太多精力。我知道的创建 Spock 模拟的唯一方法是:

T Mock(Class<T> type)   

在规范中。当不在 Specification 类中时,Spock 中是否有一些 API 可以创建 Mocks,所以我可以为 Spring 上下文创建 Spock 模拟?

【问题讨论】:

    标签: spring unit-testing spock


    【解决方案1】:

    目前无法在规范类之外创建模拟(并在另一个规范类中使用它们)。对此有一个开放的功能请求。实现这一点应该不会太难,但它需要对 spock-core 进行一些更改。至少,需要有一种方法可以手动将模拟对象附加到另一个规范实例。或许,将面向用户的模拟创建 API 移出 MockingApi 基类也是有意义的。

    您应该能够将 Mockito 与 Spock 一起使用,只要您通过调用返回 true 的辅助方法来包装包含在 then 块中的所有验证代码(因为 Spock 会将其视为断言)。类似于then: mockito { /* mockito verifications go here */ }

    【讨论】:

    • 我正坐在 SpringOne Spock 和 testMvc 会话中,并且开始感觉很明显,为了混合一些模拟的服务,以及 Spring 配置的一些服务(例如,避免在外部有集成逻辑测试)这个功能将非常有用。
    • 也许 SpecificationMixin 是我想要的?
    • 我不熟悉SpecificationMixin。正在进行中的拉取请求允许外部构建模拟,希望它将与下一个 Spock 版本一起提供。
    • @PeterNiederwieser - 您可以分享任何链接让我们更密切地跟踪进度吗?对于使用大量 Spring 以更快地采用 Spock 的 java 繁重的商店来说,这将是一个杀手级功能。
    • > 当前无法在规范类之外创建模拟(并在另一个规范类中使用它们)。 @PeterNiederwieser ^^^ 仍然正确。似乎从 Spock 1.1 开始, DetachedMockFactory 和 MockUtil.attach/detachMock 似乎支持它。但我无法让它工作。
    【解决方案2】:

    找到了在 Spring 应用程序中使用 Spock 模拟对象的简单解决方法。这里我的 spring 配置为 basar bean 使用模拟:

    @Configuration @Profile("mocking")
    class MockingContext {
      @Bean Basar basar(){ new DelegatingBasar() }
    }
    
    class DelegatingBasar implements Basar {
      @Delegate Basar delegate
    }
    

    这里有一个简单的 Spock 规范,它创建和使用模拟:

    @Autowired
    Basar basar
    Basar basarMock
    
    def setup() {
        basarMock = Mock(Basar)
        basar.delegate = basarMock;
    }
    
    def "create a new seller"(User seller) {
        given:
            basarMock.findAllUsers() >> []
        when:
            go "/static/sellers.html"
            waitFor { $("#newUser") }
            $("#newUser").click()
            waitFor { $("#basarNumber") }
            $("#basarNumber").value(seller.basarNumber)
            $("#name").value(seller.name)
            $("#lastname").value(seller.lastname)
            $("#email").value(seller.email)
            $("#saveUser").click()
            waitFor { $("#successfullCreated") }
        then:
            1 * basarMock.saveUser({ newUser ->  
                newUser.basarNumber == seller.basarNumber
                newUser.name        == seller.name
                newUser.lastname    == seller.lastname
                newUser.email       == seller.email
            })
        where:
            seller << [ new User(basarNumber: "100", name: "Christian", lastname: "", email: ""),
                        new User(basarNumber: "ABC", name: "",          lastname: "", email: "")]
    }
    

    【讨论】:

    • 这是一个巧妙的解决方法:)。与此同时,我们已经转向了一种更轻量级的方法。我们尝试完全避免在我们的单元测试中构建 Spring 上下文,而只是自己模拟所有依赖项(参见 gist.github.com/derkork/45d7fba64b54a41608e1)。这显着提高了我们的测试吞吐量。我们只将 spring 用于复杂的事情,例如 DAO 测试,我们针对内存数据库拍摄真实的语句。
    【解决方案3】:

    自 Spock 1.1 以来,可以在规范类之外创建模拟,使用 DetachedMockFactorySpockMockFactoryBean。还支持用于基于 XML 的配置的 spock 命名空间。您可以找到使用示例in the documentation

    使用基于 Java 的配置和 DetachedMockFactory 的 Spring 测试如下所示:

    @ContextConfiguration(classes = [TestConfig, DiceConfig])
    class DiceSpec extends Specification {
        @Autowired
        private RandomNumberGenerator randomNumberGenerator
    
        @Subject
        @Autowired
        private Dice dice
    
        def "uses the random number generator to generate results"() {
            when:
                dice.roll()
    
            then:
                1 * randomNumberGenerator.randomInt(6)
        }
    
        static class TestConfig {
            private final mockFactory = new DetachedMockFactory()
    
            @Bean
            RandomNumberGenerator randomNumberGenerator() {
                mockFactory.Mock(RandomNumberGenerator)
            }
        }
    }
    
    @Configuration
    class DiceConfig {
        @Bean
        Dice dice(RandomNumberGenerator randomNumberGenerator) {
            new Dice(randomNumberGenerator)
        }
    }
    

    基于 XML 的配置如下所示:

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:spock="http://www.spockframework.org/spring"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans.xsd
               http://www.spockframework.org/spring http://www.spockframework.org/spring/spock.xsd">
        <spock:mock id="randomNumberGenerator" class="RandomNumberGenerator"/>
    </beans>
    

    确保包含 spock-spring 依赖项:

    testCompile group: 'org.spockframework', name: 'spock-spring', version: '1.1-groovy-2.4-rc-3'
    

    【讨论】:

      【解决方案4】:

      这对于“纯春天”来说非常简单:

      def parentAppCtx = new StaticApplicationContext()
      parentAppCtx.beanFactory.registerSingleton("myBean", Mock(MyClass))
      parentAppCtx.refresh()
      def appCtx = new ClassPathXmlApplicationContext("spring-config.xml", parentAppCtx)
      

      当然,这是假设您正确分解了 Spring 配置。 (例如,如果您在spring-config.xml 中重新定义"myBean",则将使用spring-config.xml 中的定义,因为ApplicationContext 本质上是一个地图,并且放入其中的最新定义将获胜。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多