探索如何使用Spock 1.2将Spock的模拟和间谍自动注入到Spring上下文中。
Spock中的存根/模拟/间谍(及其生命周期)一直与Spock Specification
类紧密结合。 只能在测试类中创建它们。 因此,使用共享的预定义模拟(在单元测试和集成测试中)是有问题的。
这种情况在Spock 1.1中有所改善,但只有在基于Spring的集成测试中使用Spock模拟子系统的全新Spock 1.2(撰写本文时为1.2-RC1)时,与在Spring中对Mockito模拟使用@SpringMock
一样容易开机 让我们检查一下。
顺便说一句,除Spock 1.2-RC1之外,为了更前沿,我将使用Spring Boot 2.1.0.M2,Spring 5.1.0.RC2和Groovy 2.5.2(但所有功能都应在Spring的稳定版本中使用(引导)和Groovy 2.4)。
还有一件事。 为了简单起见,在本文中,我将使用术语“模拟”来指代存根和间谍。 它们的行为有所不同 ,但是在Spock测试中将其注入Spring上下文的范围通常并不重要。
Spock 1.1 –手动方式
多亏了LeonardBrünings的工作,Spock中的模拟才脱离了Specification
类。 最终可以在外部创建它们,然后将其附加到正在运行的测试中。 这是在Spring(或任何其他)上下文中使用Spock模拟的基础。
在此示例代码中,我们具有ShipDatabase
类,该类使用OwnShipIndex
和EnemyShipIndex
(当然是由构造函数注入的:))来返回有关所有与名称匹配的已知船只的汇总信息。
//@ContextConfiguration just for simplification, @(Test)Configuration is usually more convenient for Spring Boot tests
//Real beans can exist in the context or not
@ContextConfiguration(classes = [ShipDatabase, TestConfig/*, OwnShipIndex, EnemyShipIndex*/])
class ShipDatabase11ITSpec extends Specification {private static final String ENTERPRISE_D = "USS Enterprise (NCC-1701-D)"private static final String BORTAS_ENTERA = "IKS Bortas Entera"@Autowiredprivate OwnShipIndex ownShipIndexMock@Autowiredprivate EnemyShipIndex enemyShipIndexMock@Autowiredprivate ShipDatabase shipDatabasedef "should find ship in both indexes"() {given:ownShipIndexMock.findByName("Enter") >> [ENTERPRISE_D]enemyShipIndexMock.findByName("Enter") >> [BORTAS_ENTERA]when:List<String> foundShips = shipDatabase.findByName("Enter")then:foundShips == [ENTERPRISE_D, BORTAS_ENTERA]}static class TestConfig {private DetachedMockFactory detachedMockFactory = new DetachedMockFactory()@Bean@Primary //if needed, beware of consequencesOwnShipIndex ownShipIndexStub() {return detachedMockFactory.Stub(OwnShipIndex)}@Bean@Primary //if needed, beware of consequencesEnemyShipIndex enemyShipIndexStub() {return detachedMockFactory.Stub(EnemyShipIndex)}}
}
这些模拟是在单独的类中(在Specification
之外)创建的,因此必须使用DetachedMockFactory
(或使用SpockMockFactoryBean
)。 这些模拟必须附加(和分离)到测试实例( Specification
实例),但是它由spock-spring
模块(从1.1版本开始)自动处理。 对于从外部创建的通用MockUtil.attachMock()
,还需要使用MockUtil.attachMock()
和mockUtil.detachMock()
使其起作用。
结果,可以在Spring上下文中创建和使用模拟,但是它不是很方便,也不常用。
Spock 1.2 –一流的支持
Spring Boot 1.4通过(Mockito的)模拟为集成测试带来了新的质量。 它利用了最初于2012年在Springockito中提出的想法(当时Spring配置主要是用XML编写的:))将模拟(或间谍)自动注入到Spring(引导)上下文中。 Spring Boot团队扩展了这个想法,并且由于有了它作为内部支持的功能(通常),因此只需在测试中添加一个或两个注释即可可靠地工作。
Spock 1.2中内置了类似的基于注释的机制。
//@ContextConfiguration just for simplification, @(Test)Configuration is usually more convenient for Spring Boot tests
//Real beans can exist in the context or not
@ContextConfiguration(classes = [ShipDatabase/*, OwnShipIndex, EnemyShipIndex*/])
class ShipDatabaseITSpec extends Specification {private static final String ENTERPRISE_D = "USS Enterprise (NCC-1701-D)"private static final String BORTAS_ENTERA = "IKS Bortas Entera"@SpringBeanprivate OwnShipIndex ownShipIndexMock = Stub() //could be Mock() if needed@SpringBeanprivate EnemyShipIndex enemyShipIndexMock = Stub()@Autowiredprivate ShipDatabase shipDatabasedef "should find ship in both indexes"() {given:ownShipIndexMock.findByName("Enter") >> [ENTERPRISE_D]enemyShipIndexMock.findByName("Enter") >> [BORTAS_ENTERA]when:List<String> foundShips = shipDatabase.findByName("Enter")then:foundShips == [ENTERPRISE_D, BORTAS_ENTERA]}
}
没有太多要添加的内容。 @SpringBean
指示Spock将模拟注入到Spring上下文中。 类似地, @SpringSpy
用间谍包装真实的bean。 在@SpringBean
的情况下,需要初始化一个字段以让Spock知道我们打算使用存根还是模拟。
此外,还有一个更通用的批注@StubBeans
,用存根替换所有已定义的bean。 但是,我计划在另一篇博客文章中单独介绍它。
局限性
对于那些希望在本文演讲后立即在您的Spock测试中将所有Mockito的模拟重写为Spock的模拟的人来说,这是一个警告。 Spock的模拟物-由于其性质和与Specification
关系-具有某些局限性。 幕后的实现创建了一个代理,该代理被注入到Spring上下文中(可能)替换真实的bean(存根/模拟)或包装它们(间谍)。 该代理在特定测试(规范)类中的所有测试之间共享。 实际上,在Spring能够缓存上下文的情况下,它也可以跨越具有相同bean / mock声明的其他测试(与Mockito的模拟或通常的Spring集成测试类似的情况)。
但是,真正重要的是,代理在执行之前即被附加到测试,并在执行之后被分离。 因此,实际上,每个测试都有其自己的模拟实例(不能应用于@Shared
字段),例如将来自不同测试的交互分组并一起验证它们是有问题的(通常是很明智的,但可能会导致某些情况)复制)。 但是,使用setup
块(或在线存根)可以共享存根和交互期望。
摘要
Spock 1.2最终带来了轻松的Spock存根/模拟/间谍支持,以便在Spring上下文中使用它们,这与Spring Boot中为Mockito提供的存根相当。 将spock-spring
模块添加到项目(运行时)依赖项就足够了。 尽管有一些限制,但在Spock(集成)测试中将本机Spock的模拟子系统与外部模拟框架(例如Mockito)混合使用,却少了一点。 不错的是,它也应该在普通的Spring Framework测试(不仅是Spring Boot测试)中工作。 Guice已实现了相同的功能(但我尚未对其进行测试)。
此外,Spock 1.2还带来了其他一些变化,包括对Java 9+的更好支持,值得在您的测试套件中进行尝试(当然,请报告任何可能发现的回归bug :)。
还有一个好消息。 除了使Spock 1.2成为可能的伦纳德的工作以及大量的错误报告者和PR贡献者之外,最近以来,还有其他一些提交者正在致力于使Spock变得更好。 您可能从其他一些流行的FOSS项目中了解了其中一些。 而且,Spock 1.2 (初步)计划成为基于JUnit 4的最后一个版本,而下一个稳定的Spock版本可能是2.0,这是利用JUnit 5及其(以及其他)并行运行测试的本机能力。
这些示例是使用Spock 1.2-RC1编写的。 一旦发布,它将更新为1.2-final。 源代码可从GitHub获得。
翻译自: https://www.javacodegeeks.com/2018/09/spock-spring-beans-mocking-integration.html