定义Spock模拟行为

时间:2015-10-10 10:17:35

标签: groovy spock

我正在编写我的第一个Spock测试并阅读mocking interactions上的文档,但我仍然没有看到"森林通过树木#34;关于一些项目。

我有一个类MyRealm,它为我的应用程序执行身份验证。它有两个依赖项AuthServiceShiroAdapter。前者我想嘲笑,后者我想原样离开(如果可能的话)。这是因为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

我有什么想法可以实现这个目标吗?

2 个答案:

答案 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
}

注意上述测试,此测试......

  1. 模拟计算返回值(测试测试)
  2. 在调用authenticate和doAuth()
  3. 之间发生的任何代码

    希望这是你想要的。如果.authenticate()的逻辑中没有任何内容可以破解(它具有与getter或setter方法相同的复杂性),那么这个测试通常是浪费时间。逻辑可能破坏的唯一方法是在JVM中出现问题(完全不在此测试的责任范围内),或者某人在未来某个时间做出改变(好吧,即使在.authenticate()包含的巨大假设下也是如此不可破坏的基本逻辑,测试有一些价值)。我漫无目的的偏离主题(非常抱歉);确保保持什么&amp;你考试的原因。它将帮助您确定测试用例的优先级,同时确定组织/分离测试逻辑的最佳方法。