如何使用 grails 在单元测试中模拟 springSecurityService

Posted

技术标签:

【中文标题】如何使用 grails 在单元测试中模拟 springSecurityService【英文标题】:How to mock springSecurityService in unit tests using grails 【发布时间】:2014-06-23 03:28:55 【问题描述】:

我在我的 grails 项目中使用 Spring 安全性。我已经安装了 spring-security-core 插件和 spring-security-ui 插件。

我用于 Person 和 Authority 的域类分别是 User 和 Role

根据项目要求,我修改了我的 User.groovy 类,代码如下:

class User 
transient springSecurityService

//Mandatory Fields
String employeeId
String firstName
String lastName
String password
String emailId

//Other Fields
String mobileNumber
String address
String city
String zipCode

User manager

static hasMany = [previousPasswords: String]

boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired

static transients = ['springSecurityService']

static constraints = 
    employeeId blank: false, unique: true
    firstName blank: false
    lastName blank: false
    password blank: false, password: true, validator:  val, obj ->
        if(obj.previousPasswords)  
            return !obj.previousPasswords.contains(encode(val.toUpperCase()))
        
        return true
    
    emailId blank: false, email: true

    mobileNumber nullable: true
    address nullable: true
    city nullable: true
    zipCode nullable: true

    manager nullable: true

    previousPasswords display: false, editable: false


static mapping = 
    password column: '`password`'


Set<Role> getAuthorities() 
    UserRole.findAllByUser(this).collect  it.role  as Set


def beforeInsert() 
    previousPasswords = [encode(password.toUpperCase())]
    encodePassword()


def beforeUpdate() 
    if (isDirty('password')) 
        previousPasswords << encode(password.toUpperCase())
        encodePassword()
    


protected String encode(String pwd) 
    return springSecurityService.encodePassword(pwd)


protected void encodePassword() 
    password = springSecurityService.encodePassword(password)


我正在尝试编写单元测试来检查这个单元类的约束

我的一个测试如下所示

    void "test if employeeId can be blank or non-unique"() 
    given:

    def springSecurityService = mockFor(SpringSecurityService,true)
    springSecurityService.encodePassword()String pwd -> return null

//      def springSecurityServiceFactory = mockFor(SpringSecurityService,true)
//      def mockSpringSecurityService = Mock(SpringSecurityService)
//      mockSpringSecurityService.metaClass.encodePassword = String password ->     return null
//      User.metaClass.encodePassword =  return null 
//      User.metaClass.encode = password -> return null 
//      User.metaClass.getSpringSecurityService =  mockSpringSecurityService 

    when: 'employeeId is blank'
    def user = new User(employeeId: "   ")
    user.springSecurityService= springSecurityService

    then: 'validation fails'
    !user.validate()
    user.errors.getFieldError("employeeId").codes.contains("nullable")

    when: 'employeeId is unique'
    user = new User(employeeId: "empId1", firstName: "f_name", lastName: "l_name", password: "password", emailId: "test@hptest.com")
    user.springSecurityService= springSecurityService

    then: 'validation succeeds'
    user.validate()

    user.save(flush: true, failOnError: true)
    mockForConstraintsTests(User, [user])

    when: 'employeeId is non unique'
    def user_2 = new User(employeeId: "empId1")
    user_2.springSecurityService= springSecurityService     

    then: 'validation fails'
    !user_2.validate()
    user_2.errors.getFieldError("employeeId").codes.contains("unique")

我一直在尝试不同的方法来模拟 springSecurityService 但似乎都失败了。谁能建议一种模拟此服务的方法。

目前我收到此错误。

groovy.lang.MissingMethodException: No signature of method:                   grails.test.GrailsMock.encodePassword() is applicable for argument types: (com.hp.bots.UserSpec$_$spock_feature_0_0_closure1) values: [com.hp.bots.UserSpec$_$spock_feature_0_0_closure1@18324cd]
at com.hp.bots.UserSpec.test if employeeId can be blank or non-unique(UserSpec.groovy:25)

如果我尝试将它作为集成测试运行(不进行模拟),该测试也会失败。我无法理解我哪里出错了。

【问题讨论】:

您将不得不调用 createMock 来生成模拟并分配给用户实例,并根据 mockFor 控件的需求记录 encodePassword 【参考方案1】:

我猜如果你替换

springSecurityService.encodePassword()String pwd -> return null

  springSecurityService.demand.encodePassword()String pwd -> return null

    user.springSecurityService= springSecurityService.createMock() (grails 2.3.7) 
and for user_2

您应该能够运行此测试。

您可以在此处查看更多文档。 http://grails.org/doc/2.3.7/guide/testing.html

【讨论】:

【参考方案2】:

我通常通过使用将被调用的函数创建一个映射来模拟服务。我只会在有问题的服务上仅调用一个或两个简单函数并且不需要验证/跟踪这些调用时才推荐这种方法。在您的情况下,您给定的子句可能如下所示:

def springSecurityService = [encodePassword:  password ->
    return password
]

【讨论】:

可能还需要验证对 mock 上特定方法的调用,而 mockControl.verify 更适合

以上是关于如何使用 grails 在单元测试中模拟 springSecurityService的主要内容,如果未能解决你的问题,请参考以下文章

在 Grails 中对服务进行单元测试时如何模拟请求

单元测试控制器时无法模拟 Grails 服务方法 - MissingMethodException

Grails Spock单元测试需要模拟事务管理器

如何模拟闭包以测试 Grails 服务结果?

在 grails 控制器中模拟命令对象导致 hasErrors() 无论如何都返回 false !请帮忙

如何模拟 acegi 身份验证服务以进行单元测试?