I am migrating a big project from grails 2.5.4 to 3.3.10. Everything is going well but I have a mayor problem in my domain objects. I use to write my custom validators this way:
class Person {
String name
static constraints = {
name: nullable: false, validator: validateName
}
static validateName = {
// validation code
}
}
Grails throws the following exception
No such property: validatorTest for class: org.grails.orm.hibernate.cfg.HibernateMappingBuilder
In grails 3.x this way of defining validators seems to be broken. I know the documentation says to use this way:
name nullable: false, validator: { // code }
But it is a LOT of code to rewrite in that case.
Is there a way to use the old method of defining validators?
Thanks
See the project at https://github.com/jeffbrown/alejandroveraconstraints.
https://github.com/jeffbrown/alejandroveraconstraints/blob/master/grails-app/domain/alejandroveraconstraints/Person.groovy:
// grails-app/domain/alejandroveraconstraints/Person.groovy
package alejandroveraconstraints
class Person {
String name
static constraints = {
name nullable: false, validator: Person.validateName
}
static validateName = {
it != 'Some Bad Name'
}
}
https://github.com/jeffbrown/alejandroveraconstraints/blob/6701f61d61dbbde34f4925d1bf418448eee0a729/src/test/groovy/alejandroveraconstraints/PersonSpec.groovy:
// src/test/groovy/alejandroveraconstraints/PersonSpec.groovy
package alejandroveraconstraints
import grails.testing.gorm.DomainUnitTest
import spock.lang.Specification
class PersonSpec extends Specification implements DomainUnitTest<Person> {
void "test validation"() {
expect:
!new Person(name: 'Some Bad Name').validate()
new Person(name: 'Some Good Name').validate()
}
}
Related
I have a field in a domain class that I applied the blank: false constraint to and I write a unit test to verify that a spaces only string for the field doesn't pass the validation, but the test fails:
void 'test name cannot be blank'() {
when:
domain.name = ' '
then:
!domain.validate(['name'])
domain.errors['name'].code == 'blank'
}
I this the ConditionNotSatisfiedError error on the line !domain.validate(['name']): ConditionNotSatisfiedError
This is my domain object:
class Thing {
String name
static constraints = {
name nullable: false, blank: false, size: 1..20
}
}
You haven't indicate what version of Grails you are using and that will likely be relevant. The project at https://github.com/jeffbrown/selenablank demonstrates how this works in 3.3.3.
https://github.com/jeffbrown/selenablank/blob/master/grails-app/domain/selenablank/Thing.groovy
package selenablank
class Thing {
String name
static constraints = {
name nullable: false, blank: false, size: 1..20
}
}
https://github.com/jeffbrown/selenablank/blob/master/src/test/groovy/selenablank/ThingSpec.groovy
package selenablank
import grails.testing.gorm.DomainUnitTest
import spock.lang.Specification
class ThingSpec extends Specification implements DomainUnitTest<Thing> {
void 'test name cannot be blank'() {
when:
domain.name = ' '
then:
!domain.validate(['name'])
domain.errors['name'].code == 'blank'
}
}
I have the following Domain class with derived property lowercaseTag.
class Hashtag {
String tag
String lowercaseTag
static mapping = {
lowercaseTag formula: 'lower(tag)'
}
}
If I run the following unit test, it will fail on the last line, because lowercaseTag property is null and by default all properties have nullable: false constraint.
#TestFor(Hashtag)
class HashtagSpec extends Specification {
void "Test that hashtag can not be null"() {
when: 'the hashtag is null'
def p = new Hashtag(tag: null)
then: 'validation should fail'
!p.validate()
when: 'the hashtag is not null'
p = new Hashtag(tag: 'notNullHashtag')
then: 'validation should pass'
p.validate()
}
}
The question is how to properly write unit tests in such cases? Thanks!
As I'm sure you've figured out, the lowercaseTag cannot be tested because it's database dependent; Grails unit tests do not use a database, so the formula/expression is not evaluated.
I think the best option is to modify the constraints so that lowercaseTag is nullable.
class Hashtag {
String tag
String lowercaseTag
static mapping = {
lowercaseTag formula: 'lower(tag)'
}
static constraints = {
lowercaseTag nullable: true
}
}
Otherwise, you'll have to modify the test to force lowercaseTag to contain some value so that validate() works.
p = new Hashtag(tag: 'notNullHashtag', lowercaseTag: 'foo')
I'm new to Groovy and Grails. The Spock test for the domain object to test persist fails due to the empty string being converted to null. Here is the code.
The domain object,
class Todo {
String name
Date createdDate
String priority
String status
static constraints = {
priority blank: true
}
}
The Spock specification,
#TestFor(Todo)
class TodoSpec extends Specification {
void "test persist"() {
when:
new Todo(name: 't1', createdDate: new Date(), priority: "1", status: 'ok').save()
new Todo(name: 't2', createdDate: new Date(), priority: '', status: 'ok').save()
then:
Todo.list().size() == 2
}
}
The result of grails test-app is
Todo.list().size() == 2
| | |
| 1 false
[collab.todo.Todo : 1]
at collab.todo.TodoSpec.test persist(TodoSpec.groovy:18)
I found the empty string '' in the line new Todo(name: 't2', createdDate: new Date(), priority: '', status: 'ok') is converted to null by debugging. After google a while, I see there is a feature in Grails converting the empty strings from the web form to null to persist, which can be disabled by a configuration grails.databinding.convertEmptyStringsToNull = false in the Config.groovy. But I don't think this is the case for Spock UT. I've tried but it doesn't work as I thought.
I was wondering why the empty string is converted to null as a parameter passing to the constructor? Thanks in advance.
It is a little hinky right now, but can be made to work pretty easily. The following test passes with Grails 2.3.9...
A domain class:
// grails-app/domain/com/demo/Person.groovy
package com.demo
class Person {
String title
}
Config.groovy:
// grails-app/conf/Config.groovy
grails.databinding.convertEmptyStringsToNull = false
// ...
A unit test:
// test/unit/com/demo/PersonSpec.groovy
package com.demo
import grails.test.mixin.TestFor
import spock.lang.Specification
#TestFor(Person)
#TestMixin(grails.test.mixin.web.ControllerUnitTestMixin)
class PersonSpec extends Specification {
void "test empty string conversion"() {
when:
def p = new Person(title: '')
then:
p.title == ''
}
}
The key is applying the ContollerUnitTestMixin to the test case, even though it isn't really testing a controller. See https://jira.grails.org/browse/GRAILS-11136.
I hope that helps.
The conversion of empty strings to null is a feature of Grails databinding, so this will happen wherever databinding is used. One such place is when calling the constructor of a domain class with a map argument. If you don't want the string to be converted to null, try this:
new Todo().priority = ''
It seems docs for mongodb-1.1.0GA are outdated when it comes to unit testing section: http://springsource.github.com/grails-data-mapping/mongo/manual/ref/Testing/DatastoreUnitTestMixin.html
Following code
#TestFor(Employee)
class EmployeeTests extends GroovyTestCase {
void setUp() {
}
void tearDown() {
}
void testSomething() {
mockDomain(Employee)
def s = new Employee(firstName: "first name", lastName: "last Name", occupation: "whatever")
s['testField'] = "testValue"
s.save()
assert s.id != null
s = Employee.get(s.id)
assert s != null
assert s.firstName == "first name"
assert s['testField'] == "testValue"
}
}
fails with this error:
No such property: testField for class: Employee
Employee class is pretty straightforward:
class Employee {
String firstName
String lastName
String occupation
static constraints = {
firstName blank: false, nullable: false
lastName blank: false, nullable: false
occupation blank: false, nullable: false
}
}
So, is unit testing of dynamic attributes possible? If it is, how?
There's no out of the box support for dynamic attributes but it's fairly easy to add. I've put the following code in my setup method. It will add dynamic attributes to any domain classes you have enabled using #TestFor or #Mock.
grailsApplication.domainClasses.each { domainClass ->
domainClass.metaClass.with {
dynamicAttributes = [:]
propertyMissing = { String name ->
delegate.dynamicAttributes[name]
}
propertyMissing = { String name, value ->
delegate.dynamicAttributes[name] = value
}
}
}
I have a command object for registering user, and I want to check how old is the user. This command object has a service dependency. How can I test custom validator for my dateOfBirth property? As it looks now is taken straight from documentation, here.
class RegisterUserCommand {
def someService
String username
String password
String password2
String email
Date dateOfBirth
static constraints = {
// other constraints
dateOfBirth blank: false, validator: {val, obj ->
return obj.someService.calculateAge(val) >= 18
}
}
So basically the question is: how can I mock 'obj' parameter of the validator closure?
The easiest way to test validation on a command object is to use GrailsUnitTestCase.mockForConstraintsTests. A mock validate method will be applied to your command object, and you can just call validate() like you would outside of a test.
Here's an example of how you could write your unit test. The blank constraint isn't meaningful for dates, so I've changed it to nullable: false.
import grails.test.GrailsUnitTestCase
class RegisterUserCommandTests extends GrailsUnitTestCase {
RegisterUserCommand cmd
protected void setUp() {
super.setUp()
cmd = new RegisterUserCommand()
mockForConstraintsTests RegisterUserCommand, [cmd]
}
void testConstraintsNull() {
cmd.dateOfBirth = null
cmd.someService = [calculateAge: { dob -> 18 }]
def result = cmd.validate()
assert result == false
assert cmd.errors.getFieldErrors('dateOfBirth').code == ['nullable']
}
void testConstraintsCustom() {
cmd.dateOfBirth = new Date()
cmd.someService = [calculateAge: { dob -> 17 }]
def result = cmd.validate()
assert result == false
assert cmd.errors.getFieldErrors('dateOfBirth').code == ['validator.invalid']
}
}
Note that your service won't get injected in a unit test (it will in an integration test though), so you'll either need to mock it, as above, or create an instance and assign it to cmd.someservice.