Problems validating blank constraints with spock in a grails application
Asked Answered
C

2

7

I have a grails application with version 2.3.1 and the next configuration in BuildConfig.groovy

   dependencies {
        ...
        ..
        .
        test "org.spockframework:spock-grails-support:0.7-groovy-2.0"
    }
    plugins {
        test(":spock:0.7") {
            exclude "spock-grails-support"
        }

I have the next domain class:

class Draft {
    def grailsApplication

    String name
    String subject
    String content

    static constraints = {
        name unique: true, blank: false
        subject blank: false
    }

    static mapping = {
        content type: 'text'
    }
}

I found this post Testing Domain Constraints Using Grails 2.x & Spock 0.7 with an interesting approach to test domain class constraints.

I have a spock test:

import spock.lang.Specification

abstract class ConstraintUnitSpec extends Specification {

    String getLongString(Integer length) {
        'a' * length
    }

    String getEmail(Boolean valid) {
        valid ? "[email protected]" : "dexterm@m"
    }

    String getUrl(Boolean valid) {
        valid ? "http://www.google.com" : "http:/ww.helloworld.com"
    }

    String getCreditCard(Boolean valid) {
        valid ? "4111111111111111" : "41014"
    }

    void validateConstraints(obj, field, error) {
        println "Draft name: " + obj.name 
        def validated = obj.validate()
        if (error && error != 'valid') {
            assert !validated
            assert obj.errors[field]
            assert error == obj.errors[field]
        } else {
            assert !obj.errors[field]
        }
    }
}

import grails.test.mixin.TestFor
import spock.lang.Unroll

@TestFor(Draft)
class DraftSpec extends ConstraintUnitSpec {
     def setup() {
        mockForConstraintsTests(Draft, [new Draft(name: 'unique')])
     }

     @Unroll("test draft all constraints #field is #error")
     def "test draft all constraints"() {
          when:
          def obj = new Draft("$field": val)

          then:
          validateConstraints(obj, field, error)

          where:
          error                  | field        | val
          'nullable'             | 'name'       | null
          'nullable'             | 'subject'    | null
          'nullable'             | 'content'    | null
          'unique'               | 'name'       | 'unique'
          'valid'                | 'name'       | 'valid name'
          'valid'                | 'subject'    | 'valid subject'
          'blank'                | 'name'       | ''
          'blank'                | 'subject'    | ''
   }
}

Test fails in both blank constraints:

Draft name: null
| Failure:  test draft all constraints subject is blank(DraftSpec)
|  Condition not satisfied:

error == obj.errors[field]
|     |  |   |     ||
blank |  |   |     |subject
      |  |   |     nullable
      |  |   org.codehaus.groovy.grails.plugins.testing.GrailsMockErrors: 3 errors
      |  |   Field error in object 'Draft' on field 'name': rejected value [null]; codes [Draft.name.nullable.error.Draft.name,Draft.name.nullable.error.name,Draft.name.nullable.error.java.lang.String,Draft.name.nullable.error,draft.name.nullable.error.Draft.name,draft.name.nullable.error.name,draft.name.nullable.error.java.lang.String,draft.name.nullable.error,Draft.name.nullable.Draft.name,Draft.name.nullable.name,Draft.name.nullable.java.lang.String,Draft.name.nullable,draft.name.nullable.Draft.name,draft.name.nullable.name,draft.name.nullable.java.lang.String,draft.name.nullable,nullable.Draft.name,nullable.name,nullable.java.lang.String,nullable]; arguments [name,class Draft]; default message [Property [{0}] of class [{1}] cannot be null]
      |  |   Field error in object 'Draft' on field 'subject': rejected value [null]; codes [Draft.subject.nullable.error.Draft.subject,Draft.subject.nullable.error.subject,Draft.subject.nullable.error.java.lang.String,Draft.subject.nullable.error,draft.subject.nullable.error.Draft.subject,draft.subject.nullable.error.subject,draft.subject.nullable.error.java.lang.String,draft.subject.nullable.error,Draft.subject.nullable.Draft.subject,Draft.subject.nullable.subject,Draft.subject.nullable.java.lang.String,Draft.subject.nullable,draft.subject.nullable.Draft.subject,draft.subject.nullable.subject,draft.subject.nullable.java.lang.String,draft.subject.nullable,nullable.Draft.subject,nullable.subject,nullable.java.lang.String,nullable]; arguments [subject,class Draft]; default message [Property [{0}] of class [{1}] cannot be null]
      |  |   Field error in object 'Draft' on field 'content': rejected value [null]; codes [Draft.content.nullable.error.Draft.content,Draft.content.nullable.error.content,Draft.content.nullable.error.java.lang.String,Draft.content.nullable.error,draft.content.nullable.error.Draft.content,draft.content.nullable.error.content,draft.content.nullable.error.java.lang.String,draft.content.nullable.error,Draft.content.nullable.Draft.content,Draft.content.nullable.content,Draft.content.nullable.java.lang.String,Draft.content.nullable,draft.content.nullable.Draft.content,draft.content.nullable.content,draft.content.nullable.java.lang.String,draft.content.nullable,nullable.Draft.content,nullable.content,nullable.java.lang.String,nullable]; arguments [content,class Draft]; default message [Property [{0}] of class [{1}] cannot be null]
      |  Draft : (unsaved)
      false
      6 differences (25% similarity)
      (b-)l(-)a(nk-)
      (nu)l(l)a(ble)

    at ConstraintUnitSpec.validateConstraints(ConstraintUnitSpec.groovy:29)
    at DraftSpec.test draft all constraints(DraftSpec.groovy:18)
| Completed 8 spock tests, 2 failed in 0m 6s
| Tests FAILED  - view reports in /Users/sdelamo/Documents/Developer/bitbucket/concertados-webapp/target/test-reports

Problem

Draft's name field is set as null when I pass '' as value

What am I missing?. Thanks in advance.

SIMPLE TEST fails as well

def "test blank constraints"() {
          when: 'the name and subjects are blank'
          def d = new Draft(name: '', subject:'')

          then: 'the validation should fail with blank errors'
          !d.validate()
          'blank' == d.errors["name"]
          'blank' == d.errors["subject"]
   }

This is the error:

| Running 1 spock test... 1 of 1
--Output from test blank constraints--
| Error --Output from test blank constraints--
| Failure:  test blank constraints(com.softamo.concertados.DraftSpec)
|  Condition not satisfied:

'blank' == d.errors["name"]
        |  | |     |
        |  | |     nullable
        |  | org.codehaus.groovy.grails.plugins.testing.GrailsMockErrors: 3 errors
Curator answered 7/11, 2013 at 8:26 Comment(5)
Isn't it because you're calling: def obj = new Draft(subject:''), and it's failing because name is null?Brickle
name is null as well when I thought it should be ''Curator
I can't see where you're setting name to '' on the last test in the table 'blank' | 'subject' | ''Brickle
I updated the question with a more explanatory test which fails alsoCurator
The docs for nullable have a pull out block about Grails converting '' to null by default: "...the data binder will convert blank strings to null"Brickle
C
7

Time_yates pointed to the issue:

http://grails.org/doc/latest/ref/Constraints/nullable.html

Web requests resulting from form submissions will have blank strings, not null, for input fields that have no value. Keep this in mind when doing mass property binding to properties that are not nullable. The default behavior is such that a blank string will not validate for nullable: false since the data binder will convert blank strings to null. This includes empty strings and blank strings. A blank string is any string such that the trim() method returns an empty string. To turn off the conversion of empty strings to null set the grails.databinding.convertEmptyStringsToNull property to false in Config.groovy. See the data binding section for more details on data binding.

Curator answered 8/11, 2013 at 9:14 Comment(0)
F
0

For Grails 2.3.9, this test highlights problems with the blank constraint, even when using the configuration settings mentioned above (checked in the then statements).

Haven't been able to track down the problem. BlankConstraint doesn't check for null property, and the blank and nullable constraints are both vetoable, so if one fails, all others are ignored.

Class under test:

package test

class TestVetoableConstraints {
    String nullableAndBlank
    String nullableAndNotBlank
    String notNullableAndBlank
    String notNullableAndNotBlank

    static constraints = {
    nullableAndBlank(nullable:true, blank:true)
    nullableAndNotBlank(nullable:true, blank:false)
    notNullableAndBlank(nullable:false, blank:true)
    notNullableAndNotBlank(nullable:false, blank:false)
    }
}

Test:

package test

import grails.test.mixin.TestFor
import spock.lang.Specification
import grails.util.Holders

@TestFor(TestVetoableConstraints)
class TestVetoableConstraintsSpec extends Specification {
    def config = Holders.config

    void "test nullable and blank constraints" (error, field, val) {
        when: 
            mockForConstraintsTests(TestVetoableConstraints)
            def obj = new TestVetoableConstraints("$field": val)

        then: 
// These are set to ensure that an empty string isn't trimmed and converted to null
            false == config.grails.databinding.convertEmptyStringsToNull
            false == config.grails.databinding.trimStrings
            validateConstraints(obj, field, error)

        where:
            error                  | field                      | val
            'valid'                | 'nullableAndBlank'         | null
            'valid'                | 'nullableAndBlank'         | ''
            'valid'                | 'nullableAndBlank'         | 'Good String'
            'valid'                | 'nullableAndNotBlank'      | null
// The next test should return blank, but actually fails on "assert obj.errors[field]" which is null
            'blank'                | 'nullableAndNotBlank'      | ''
            'valid'                | 'nullableAndNotBlank'      | 'Good String'
            'nullable'             | 'notNullableAndBlank'      | null
// The next test should be valid, but actually fails on "assert !obj.errors[field]" which returns 'nullable'
            'valid'                | 'notNullableAndBlank'      | ''
            'valid'                | 'notNullableAndBlank'      | 'Good String'
            'nullable'             | 'notNullableAndNotBlank'   | null
// The next test should return blank, but actually fails on "assert error == obj.errors[field]" because it returns 'nullable'
            'blank'                | 'notNullableAndNotBlank'   | ''
            'valid'                | 'notNullableAndNotBlank'   | 'Good String'
    }

    private void validateConstraints(obj, field, error) {
       def validated = obj.validate()
       if (error && error != 'valid') {
           assert !validated
           assert obj.errors[field]
           assert error == obj.errors[field]
       } else {
           assert !obj.errors[field]
       }
   }
}
Fraase answered 12/6, 2014 at 15:26 Comment(1)
It seems this is a known issue. I found an open defect on this: GRAILS-11136Fraase

© 2022 - 2024 — McMap. All rights reserved.