如何在规范类之外创建Spock模拟?

14
我们将Spock测试与Spring的@ContextConfiguration结合使用,以便在Spring上下文中构建bean,并使用Spock进行实际测试。我们希望将Spock模拟注入到我们的Spring bean中。对于Mockito,有一个扩展程序,允许您执行以下操作:
 <mockito:mock id="accountService" class="org.kubek2k.account.DefaultAccountService" />

然后将这个模拟对象引用到其他Spring Beans中。看起来Spock没有这样的扩展。然而,如果您知道如何在Specification类之外创建Mocks,则构建这个功能可能不需要太多努力。我所知道的创建Spock Mock的唯一方法是:

T Mock(Class<T> type)   

在规范中。Spock中是否有一些API可以在不在Specification类内部时创建Mocks,这样我就可以为Spring上下文创建Spock mocks了吗?
4个回答

7
自Spock 1.1版本以来,使用DetachedMockFactorySpockMockFactoryBean可以创建规范类之外的模拟。同样支持基于XML的配置的spock命名空间。您可以在文档中找到用法示例。
使用Java-based配置和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'

6
在一个规范类之外创建模拟对象(并在另一个规范类中使用它们)目前是不可能的。这方面已经有一个开放的功能请求。实现起来应该不会太难,但需要对 spock-core 进行一些修改。至少,需要有一种方法手动将模拟对象附加到另一个规范实例上。可能还有必要将面向用户的模拟创建 API 移出 MockingApi 基类。
只要将包含在 then 块中的所有验证代码都包装在返回 true 的辅助方法调用中(因为 Spock 将其视为断言),就可以使用 Mockito 和 Spock。像这样:then: mockito { /* mockito 验证放在这里 */ }。

4
我正在参加SpringOne Spock & testMvc的会议,现在已经很明显,在测试中为了混合使用一些模拟服务和一些由Spring配置的服务(例如,避免在测试之外编写集成逻辑),这个功能将非常有用。 - Karl the Pagan
也许 SpecificationMixin 就是我正在寻找的东西? - Karl the Pagan
我不熟悉 SpecificationMixin。目前有一个拉取请求正在进行中,允许外部构造模拟对象,希望它能随着下一个Spock版本一起发布。 - Peter Niederwieser
@PeterNiederwieser- 你能分享任何链接让我们更紧密地跟踪进展吗?这将是一个杀手级功能,对于那些使用很多Spring的Java重型商店来更快地采用Spock。 - cdeszaq
在规范类之外创建模拟对象(并在另一个规范类中使用它们)目前是不可能的。 @PeterNiederwieser 是否仍然正确。似乎自 Spock 1.1 版本以来,DetachedMockFactory 和 MockUtil.attach/detachMock 已经支持此功能。但我在尝试时遇到了一些问题。 - Farrukh Najmi

5

我找到了一个简单的解决方法,可以在Spring应用程序中使用Spock模拟对象。以下是我使用模拟对象替代basar bean的Spring配置:

@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: "")]
}

1
这是一个巧妙的解决方法 :). 与此同时,我们已经转向了更轻量级的方法。我们尽量避免在单元测试中构建Spring上下文,并自行模拟所有依赖项(请参见https://gist.github.com/derkork/45d7fba64b54a41608e1)。这显著提高了我们的测试吞吐量。我们仅在复杂的DAO测试等复杂情况下使用Spring,在这些情况下,我们会对内存数据库进行实际语句测试。 - Jan Thomä

0

使用“纯 Spring”非常简单:

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本质上是一个Map,最近放入其中的定义将获胜。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接