Grails 单元测试空奇怪

Grails Unit Test Null Strangeness

我在 grails 中有一组基本的单元测试,但我遇到了一些奇怪的、不一致的行为。所以基本上单元测试是针对与我的用户域交互的服务。

问题很严重 - 如果我 运行 完整的测试规范 - 每次都有 4 个测试失败。如果我 运行 他们单独 - 他们通过。这里显而易见的是,在测试之间存在状态 - 但鉴于这是一个单元测试 - 不应该存在(并且我已经通过日志记录验证了这一点)。

这里使用的组件是:

所以查看规范,我在设置中有以下内容:

User user

def setup() {

    println "================ Setting Up User (${User.count()}) ================"

    // Set the roles
    new Role(authority: "ROLE_ADMIN").save()
    new Role(authority: "ROLE_OPERATOR").save()
    def role = new Role(authority: "ROLE_USER").save()

    def userAddress = new Address()
    userAddress.with {
        name = "Name"
        address1 = "Address 1"
        address2 = "Address 2"
        townCity = "Town"
        postcode = "BT19"
        county = "Down"
    }

    // Create an admin user
    user = new User()
    user.with {
        christianName = "Phil"
        surname = "Preston"
        email = "p@p.com"
        username = "admin"
        password = "password123"
        phone = ""
        skype = ""
        address = userAddress
    }

    println(user.properties)

    // Save the test user
    if (user.validate()) {
        println "Saving"
        user.save(flush: true, failOnErrors: true)
        UserRole.create(user, role)
    }
    else {
        user.errors.allErrors.eachWithIndex { i, x ->
            println "${i} - ${x}"
        }
    }


    assert user
}

而两个简单的测试如下:

void "Test updateUser - changing a user password"() {

    given: "An update to the user"
    UserCommand cmd = new UserCommand()
    cmd.with {
        id = user.id
        christianName = user.christianName
        surname = user.surname
        username = user.username
        email = user.email
        skype = user.skype
        phone = user.phone
        password = "newPassword"
        passwordCheck = "newPassword"
        isAdmin = user.isAdmin()
        isOperator = user.isOperator()
    }

    when: "attempt to update"
    User updated = service.updateUser(cmd)

    then: "the password should be update - but nothing else"
    updated.password == cmd.password
}

void "Test updateUser - changing a user detail"() {

    given: "An update to the user"
    UserCommand cmd = new UserCommand()
    cmd.with {
        id = user.id
        christianName = user.christianName
        surname = user.surname
        username = user.username
        email = "update@update.com"
        skype = user.skype
        phone = user.phone
        password = user.password
        isAdmin = user.isAdmin()
        isOperator = user.isOperator()
    }

    when: "attempt to update"
    User updated = service.updateUser(cmd)

    then: "the email should be update - but nothing else"
    updated.email == cmd.email
}

(还有其他的-但这都是为了证明问题)

所以奇怪的是:

单元测试失败,因为我正在更新的用户域对象验证失败时抛出异常。所以如下:

def updateUser(UserCommand userCommand) {
    assert userCommand

    // Get the current
    User foundUser = User.findById(userCommand.id)
    def wasAdmin = foundUser.admin
    def wasOperator = foundUser.operator

    // Bind Data
    bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])
    foundUser?.address?.with {
        name = userCommand.name
        address1 = userCommand.address1
        address2 = userCommand.address2
        townCity = userCommand.townCity
        county = userCommand.county
        postcode = userCommand.postcode
    }

    // THIS VALIDATION FAILS ON SECOND TEST
    if (foundUser.validate()) {
        foundUser.save() 
        // ... removed 
        return foundUser.refresh()
    }
    else {
        Helper.copyErrorToCmd(foundUser, userCommand)
        log.error("Errors: ${foundUser.errors.allErrors}")
        throw new UserServiceException(cmd: userCommand, message: "There were errors updating user")
    }

User域对象上的错误基本上是(缩短):

因此您可以看到我为这些字段设置了空字符串 - 并且转换为 null(Grails 会这样做),这就是原因。然而,问题是:

所以User域对象中的约束如下:

static constraints = {
    christianName blank: false
    surname blank: false
    username blank: false, unique: true, size: 5..20
    password blank: false, password: true, minSize: 8
    email email: true, blank: false
    phone nullable: true
    skype nullable: true
    address nullable: true
}

我正在使用具有以下约束的 Command 对象:

static constraints = {
    importFrom User
    importFrom Address
    password blank: false, password: true, minSize: 8
    passwordCheck(blank: false, password: true, validator: { pwd, uco -> return pwd == uco.password })
}

请注意,我什至还尝试在 UserCommand 约束闭包中添加 Skype 和 phone 字段。

如果我在 setup 中的字段中添加文本,这个问题就会消失,但在实际应用程序中这是不可能的,因为这些字段可以留空。

对于这种不一致是如何发生的任何帮助,我们将不胜感激。

休闲

我添加了一个最小的 GitHub 项目来重现问题:Github Project Recreating Issue。看问题:

./grailsw test-app

请问运行两次考试,第一次通过第二次不通过。到 运行 单独失败的测试 运行:

./gradlew test --tests "org.arkdev.bwmc.accountmission.UserServiceSpec.*Test updateUser - changing a user detail"

你可以看到测试通过。

问题似乎是 skypephone 字段在第一次测试中是 nullable:true,在第二次测试中是 nullable:false(我进入 user.validate(),然后进入 GrailsDomainClassValidator.java,在 validate(Object,Errors,boolean) 调用中我可以看到 constrainedProperties 映射(基本上是字段 -> 约束)。这表明 skype 是 NullableConstraint.nullable == true,在第一次调用设置时,但在第二次测试的设置调用中显示 NullableConstraint.nullable == false。)。这没有意义。

您不必从技术上测试框架中的约束,但我之前遇到过类似的问题,所以我明白您为什么要这样做。

可以post你的完整测试吗?你在使用@TestFor/@Mock吗?就像检查一样,但我假设你必须有 @Mock 否则你会在创建域 类 时遇到不同的错误? @Mock 是使用约束所必需的(或@GrailsUnitTestMixin afaik)。

你在评论里说"The values in your app can be blank";应该注意的是,这也与可为空的不同。如果它真的可以是空白的,你应该 change/add 它然后 "" 就可以了

您也没有使用 'failOnError: true',因为您打错了。

抱歉,我没有足够的代表来写评论。

第一次通话后

bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])

UserService.updateUser(UserCommand) 中(由特征方法 Test updateUser - changing a user password 调用)静态成员 User.constraints 变为 null。去搞清楚。我不知道 Grails 是如何工作的,所以也许其他人可以理解它。但对我来说,这似乎不应该发生。也许你在那个调用中不知何故犯了一个错误。