Grails Unit Test Null Strangeness

时间:2017-04-05 09:25:17

标签: unit-testing grails null gorm spock

我在grails中有一组基本的单元测试,并且我得到一些奇怪的,不一致的行为。所以基本上单元测试是用于与我的用户域交互的服务。

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

这里的组件是:

  • UserService
  • 用户域
  • 地址域
  • UserCommand(Verifiable)

所以看看Spec我在设置中有以下内容:

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
}

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

所以奇怪的是:

  • 第一次测试通过,第二次测试失败。
  • 如果我交换订单 - 第一个仍然通过,第二个失败
  • 如果我单独运行 - 他们都将通过
  • setup()中的代码每次打印用户对象数(0)
  • 它验证每次创建对象(assert和日志记录)

单元测试失败,因为我在用户域对象验证失败时抛出异常。以下是:

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")
    }

用户域对象上的错误基本上(缩短):

  • 'skype':被拒绝的值[null]
  • 'phone':拒绝值[null]

所以你可以看到我有这些字段的空字符串 - 并且转换为null(如Grails所做的那样),这就是原因。然而,问题是:

  • 两者都应该失败,当单独运行时肯定会失败
  • 两个字段在约束
  • 中标记为nullable: true
  • 在UserService中我使用调试器检查运行两个测试时正在保存的对象 - 两个测试的phone和skype都为null,但是一个失败,一个没有

因此,用户域对象中的约束如下:

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和电话字段。

如果我向setup中的字段添加文字,则此问题就会消失,但实际应用中的字段不可能,因为这些字段可以留空。

非常感谢任何关于如何发生这种不一致的帮助。

我添加了一个重新创建问题的最小GitHub项目:Github Project Recreating Issue。要查看问题:

./grailsw test-app

将运行两次测试,第一次传递第二次失败。要单独运行失败的测试:

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

你可以看到测试通过。

问题似乎是skypephone字段可以为空:第一次测试为true,第二次测试为nullable:false(我进入user.validate(),进入GrailsDomainClassValidator.java,在validate(Object,Errors,boolean)调用中我可以看到constrainedProperties地图(基本上是字段 - >约束)。这表明Skype是NullableConstraint.nullable == true,在第一次调用setup,但是在第二次测试的安装调用中显示NullableConstraint.nullable == false。)。这毫无意义。

2 个答案:

答案 0 :(得分:0)

你不应该在技术上测试框架中的约束条件,但我之前遇到过类似的问题,所以我可以看到你想要的原因。

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

您在评论中说#34;您应用中的值可以为空白&#34 ;;应该注意的是,这也是可空的。如果它真的可以是空白你应该更改/添加它然后""会工作

你也没有使用' failOnError:true'正如你把它拼错了一样。

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

答案 1 :(得分:0)

第一次通话后

bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])
UserService.updateUser(UserCommand)中的

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