我正在编写我的第一个Spock测试并阅读mocking interactions上的文档,但我仍然没有看到"森林通过树木#34;关于一些项目。
我有一个类MyRealm
,它为我的应用程序执行身份验证。它有两个依赖项AuthService
和ShiroAdapter
。前者我想嘲笑,后者我想原样离开(如果可能的话)。这是因为AuthService
实际上与LDAP建立了后端连接,所以我想模仿它。但是ShiroAdapter
只定义了几个将我的对象转换为Apache Shiro安全对象(主体,权限等)的实用方法。所以它可以不被嘲笑(methinks)。
class MyRealmSpec extends Specification {
MyRealm realm
def setup() {
AuthService authService = Mock(AuthService)
// configure 'authService' mock <-- ?????
ShiroAdapter shiroAdapter = new ShiroAdapter()
realm = new MyRealm(authService: authService,
shiroAdapter: shiroAdapter)
}
def "authenticate throws ShiroException whenever auth fails"() {
when:
realm.authenticate('invalid_username', 'invalid_password')
then:
Throwable throwable = thrown()
ShiroException.isAssignableFrom(throwable)
}
}
我相信我非常接近,但我很难配置模拟以我想要的方式进行测试。 Spock文档(上面链接)似乎只记录了如何验证调用模拟方法的次数。我对此不感兴趣。
此处,MyRealm#authenticate(String,String)
调用了AuthService#doAuth(String,String)
。所以我需要我的模拟AuthService
实例来简单地返回false
(表示失败的身份验证)或者如果出现意外情况则抛出ServiceFaulException
。
我有什么想法可以实现这个目标吗?
答案 0 :(得分:1)
您非常接近,检查抛出的异常类型的简单方法是将Exception
类放在括号中。例如:
def "authenticate throws ShiroException whenever auth fails"() {
when:
realm.authenticate('invalid_username', 'invalid_password')
then:
thrown(ShiroException)
}
您还需要模拟LDAP服务调用本身并模拟异常或失败的登录。模拟操作在测试的then子句中。
def "authenticate throws ShiroException whenever auth fails"() {
setup:
String invalidUserName = 'invalid_username'
String invalidPassword = 'invalid_password'
when:
realm.authenticate(invalidUserName, invalidPassword)
then:
1 * authService.doAuth(invalidUserName, invalidPassword) >> returnClosure
thrown(ShiroException)
where:
returnClosure << [{throw new ShiroException()}, { false }]
}
请注意,您需要让模拟语句上的参数匹配或使用通配符匹配。
要匹配任何String,您可以使用下划线语法:
1 * authService.doAuth(_, _) >> false
答案 1 :(得分:0)
您可能会对一些不同的行为对象感兴趣。
Stub - 您只定义返回的内容
MyObject obj = Stub{method >> null}
模拟 - 您定义返回的内容和/或调用方法的次数
MyObject obj = Mock {1..3 methodCall >> false}
间谍 - 它会创建你的对象,但你可以将特定方法覆盖为模拟(并且你的覆盖仍然可以调用原始代码)
MyObject obj = Spy {methodCall >> false}
obj.otherMethodCall() // Calls code like normal
obj.methodCall() // Returns false like we told it to
听起来你需要一个存根,但你可以使用一个没有任何问题的模拟。我提到间谍,因为如果你的对象是自我依赖的(将来),它可以挽救生命。
def "authenticate throws ShiroException whenever auth fails"() {
given:
AuthService authService = Stub(AuthService)
authService.doAuth(_,_) >> expectedError
MyRealm realm = new MyRealm(
authService: authService,
shiroAdapter: new ShiroAdapter())
when:
realm.authenticate("just enough to get", "to the doAuth method")
then:
thrown(ShiroException)
where:
expectedError << [ShiroException, /*other exceptions this method has to test*/]
}
不需要数据/逻辑分离,但它是使测试更灵活和可维护的好方法。虽然在这种情况下它并不是真的需要,因为你只有一个例外要扔。
我实际上会将失败的身份验证和异常身份验证测试分开。他们正在研究根本不同的行为,测试这两种情况的测试逻辑有些不同。为了保持可操作性/灵活性,每次测试都避免测试太多(或太少),这符合您的利益。
def "authenticate throws ShiroException whenever auth fails"() {
given:
AuthService authService = Stub(AuthService)
authService.doAuth(_,_) >> { args ->
return args[0] == good && args[1] == good
}
MyRealm realm = new MyRealm(
authService: authService,
shiroAdapter: new ShiroAdapter())
expect:
realm.authenticate(username, password) == expectedAuthentication
where:
userName | password | expectedAuthentication
bad | good | false
bad | bad | false
good | good | true
}
注意上述测试,此测试......
希望这是你想要的。如果.authenticate()的逻辑中没有任何内容可以破解(它具有与getter或setter方法相同的复杂性),那么这个测试通常是浪费时间。逻辑可能破坏的唯一方法是在JVM中出现问题(完全不在此测试的责任范围内),或者某人在未来某个时间做出改变(好吧,即使在.authenticate()包含的巨大假设下也是如此不可破坏的基本逻辑,测试有一些价值)。我漫无目的的偏离主题(非常抱歉);确保保持什么&amp;你考试的原因。它将帮助您确定测试用例的优先级,同时确定组织/分离测试逻辑的最佳方法。