Grails如何对异常执行单元测试

Posted

技术标签:

【中文标题】Grails如何对异常执行单元测试【英文标题】:Grails how to perform unit test on exceptions 【发布时间】:2019-06-02 20:04:09 【问题描述】:

我正在尝试测试帐户过期异常。

def authfail() 
    String msg = ''
    def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]

//        println("print exception: $exception | $session | $springSecurityService.getCurrentUser()")
    if (exception) 
        if (exception instanceof AccountExpiredException) 
            msg = message(code: 'springSecurity.errors.login.expired')
        
        else if (exception instanceof CredentialsExpiredException) 
            msg = message(code: 'springSecurity.errors.login.passwordExpired')
        
        else if (exception instanceof DisabledException) 
            msg = message(code: 'springSecurity.errors.login.disabled')
        
        else 
            msg = message(code: 'springSecurity.errors.login.fail')
        
    

    if (springSecurityService.isAjax(request)) 
        render([error: msg] as JSON)
    
    else 
        flash.message = msg
        redirect action: 'auth', params: params
    

我在意识到自己被卡住之前尝试编写上面的测试用例,因为我不知道如何触发过期登录,以便满足抛出 AccountExceptionExpired 异常的单元测试标准。

void "test authFail"() 

when:
    session."$WebAttributes.AUTHENTICATION_EXCEPTION" = new AccountExpiredException( 'This account has expired' )
    def logexp = controller.authfail()
then:
    logexp == 'springSecurity.errors.login.expired'
when:
    session."$WebAttributes.AUTHENTICATION_EXCEPTION" = new CredentialsExpiredException( 'This credentials have expired' )
    def passexp = controller.authfail()
then:
    passexp == 'springSecurity.errors.login.passwordExpired'
when:
    session."$WebAttributes.AUTHENTICATION_EXCEPTION" = new DisabledException( 'The account is disabled' )
    def logdis = controller.authfail()
then:
    logdis == 'springSecurity.errors.login.disabled'
when:
    session."$WebAttributes.AUTHENTICATION_EXCEPTION" = new UnsupportedOperationException( 'Sorry, we were not able to find a user with that username and password.' )
    def logfail = controller.authfail()
then:
    logfail == 'springSecurity.errors.login.fail'
when:
    controller.authfail()
then:
    1 * springSecurityService.isAjax( _ ) >> true
    controller.response.json == [error :'springSecurity.errors.login.fail']

    

【问题讨论】:

【参考方案1】:

以下内容将测试您的大部分方法:

import grails.plugin.springsecurity.SpringSecurityService
import grails.test.mixin.TestFor
import org.springframework.security.authentication.AccountExpiredException
import org.springframework.security.authentication.CredentialsExpiredException
import org.springframework.security.authentication.DisabledException
import org.springframework.security.web.WebAttributes
import spock.lang.Specification

@TestFor(YourController)
class YourControllerSpec extends Specification 

def springSecurityService = Mock( SpringSecurityService )

void setup() 
    controller.springSecurityService = springSecurityService


void "test authFail"() 
    given:
        session."$WebAttributes.AUTHENTICATION_EXCEPTION" = new AccountExpiredException( 'This account has expired' )
    when:
        controller.authfail()
    then:
        flash.message == 'springSecurity.errors.login.expired'
    when:
        session."$WebAttributes.AUTHENTICATION_EXCEPTION" = new CredentialsExpiredException( 'This credentials have expired' )
        controller.authfail()
    then:
        flash.message == 'springSecurity.errors.login.passwordExpired'
    when:
        session."$WebAttributes.AUTHENTICATION_EXCEPTION" = new DisabledException( 'The account is disabled' )
        controller.authfail()
    then:
        flash.message == 'springSecurity.errors.login.disabled'
    when:
        session."$WebAttributes.AUTHENTICATION_EXCEPTION" = new UnsupportedOperationException( 'Bad stuff' )
        controller.authfail()
    then:
        flash.message == 'springSecurity.errors.login.fail'
    when:
        controller.authfail()
    then:
        1 * springSecurityService.isAjax( _ ) >> true
        response.json == [error :'springSecurity.errors.login.fail']


会话只是一个映射,我们向其中添加字符串常量的键和异常的值。 对于除最后一个测试之外的所有测试,我们将进入最后一个 else 块,在最后一个测试中,我们为 `isAjax' 返回 true。

【讨论】:

我尝试了您的建议并得到了这个结果。 imgur.com/nuO1cX7 您是否按照建议从authfail 返回msg 我对此非常陌生。我只实现了你的代码并得到了我不太理解的结果,为什么 res NULL 当它已经在上面定义为控制器时。 它应该可以在没有msg 的显式返回的情况下工作,但至少更容易阅读具有显式返回的代码,您是否在测试类中导入了org.springframework.security.web.WebAttributesorg.springframework.security.authentication.AccountExpiredException 类? 是的,我已经导入了这两个,在我导入之前它甚至无法运行【参考方案2】:

虽然这不是 Grails,但它是 SpringBoot 2.0。

如果 failureHandler 暴露为 bean,则可以窥探它。

@SpyBean
AuthenticationFailureHandler failureHandler;

并简单地验证异常已被抛出

Mockito.verify(failureHandler).onAuthenticationFailure(
    any(),
    any(),
    any(AccountExpiredException.class)
);

一个简单的test 可以是这样的:

@Test
public void accountExpired() throws Exception 
    doReturn(user
        .username("expired")
        .accountExpired(true)
        .build()
    ).when(userDetailsService).loadUserByUsername(any(String.class));
    mvc.perform(
        MockMvcRequestBuilders.post("/login")
            .param("username", "expired")
            .param("password", "password")
    )
        .andExpect(status().is4xxClientError())
        .andExpect(unauthenticated())
    ;
    Mockito.verify(failureHandler).onAuthenticationFailure(
        any(),
        any(),
        any(AccountExpiredException.class)
    );

https://github.com/fhanik/spring-security-community/的所有样本

【讨论】:

以上是关于Grails如何对异常执行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

Grails 单元测试问题

如何对使用转换器的 Grails 服务进行单元测试?

单元测试 Grails 控制器链接

在 Grails 2.2 中是不是可以对 mongodb 动态属性进行单元测试?

Grails 2.3 使用 IVY 解析器进行单元测试

使用 Grails 对域类进行单元测试