下面有一个非常简单的方法。它会调用另一种方法,该方法先删除API密钥,然后再调用另一种方法来创建新密钥并返回。
下面的测试也只是检查两个方法是否被正确调用。但是尽管在这两种方法上仍然会收到0调用错误。是什么原因导致这个问题?
AuthApiKeyPair updateApiKeyPair(AuthApiKeyPair apiKeyPair, Boolean createNewKey) {
AuthApiKeyPair newKeyPair
if (createNewKey) {
deleteApiKeyPair(apiKeyPair)
//The key will be created with the same info as the previous key.
newKeyPair = createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
}
newKeyPair
}
测试:
def "should soft delete key pair and create new one"() {
setup:
AuthApiKeyPair apiKeyPair = AuthApiKeyPair.build(acquirerId: 123, source: PaymentSource.BOARDING_API, label: 'Boarding API key')
when:
service.updateApiKeyPair(apiKeyPair, true)
then:
1 * service.deleteApiKeyPair(apiKeyPair)
1 * service.createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
}
答案 0 :(得分:4)
如果考虑测试,您将意识到,在最佳情况下,它会测试Spock的模拟机制,而不是您的业务代码。您尚未向我们展示完整的测试规范类,但是根据您的情况,我们可以假设测试中的service
只是一个模拟。如果是这样,那么您就不能期望这两个调用:
then:
1 * service.deleteApiKeyPair(apiKeyPair)
1 * service.createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
发生。仅仅是因为模拟类不能执行真实的方法。
我强烈建议您测试一个真实的类,而不是测试特定方法导致哪种类型的调用,而是调用特定方法的预期(确定性)结果是什么。您可以在service.updateApiKeyPair(apiKeyPair, true)
子句中的真实对象上执行when
,然后可以检查是否创建了新的API密钥对(并持久保存在您使用的存储中),以及旧的对密钥是否已不存在。与仅检查调用相比,这种测试至少具有一些好处:
service.updateApiKeyPair()
的实现,只要它产生相同的结果,您的测试仍然有用(因为该测试不会像调用测试那样限制您的实现),当然,可能需要进行一些设计更改。我猜想您的服务类使用一些注入的DAO或存储库,这些存储库会保留API密钥对。考虑为测试提供这种DAO的内存实现-一个类,而不是将对象持久存储在真实数据库中,而是将所有对象存储在内部ConcurrentMap<K,V>
中。因此,您仍然可以将测试作为单元测试运行,并且可以测试将createNewKey
参数设置为true
的API密钥对是否完全符合您的期望。或者,您可以编写一个替换H2数据库的集成测试,但这只会使您的测试引导时间更长。选择是您的。
有一条值得记住的规则-如果您的类/组件/功能等难以进行单元测试,则意味着选择了错误的设计。
Spy
对象最后我故意提到一件事。 Spock支持所谓的“间谍”对象,其行为类似于真实的对象,但是它们使您可以存根某些零件并将其作为模拟对象对待,例如。调用计数。但是,即使Spock的作者也警告开发人员使用此功能:
(使用此功能之前请三思。更改规范下的代码设计可能会更好。)
来源:http://spockframework.org/spock/docs/1.0/interaction_based_testing.html#spies
我不知道Grails是否具有用于创建Spy而不是Mock的测试的注释,但是您始终可以follow official documentation并创建普通的Spock单元测试以将您的服务实例化为Spy,然后您就可以尝试计算调用次数。我不建议这样做,只是为了记录而提及。