Grails - testing custom validator on domain class issue - grails

I'm learning grails from Grails - getting started by Jason Rudolph book.
My domain class looks like that:
class Race {
String name;
Date startDateTime
String city
String state
Float distance
Float cost
Integer maxRunners = 10000
static hasMany = [registrations: Registration]
static constraints = {
name(maxSize: 50, blank: false)
startDateTime(validator: {
return it > new Date()
})
city(maxSize: 30, blank: false)
state(inList: ['GA', 'NC', 'SC', 'VA'], blank: false)
distance(min: 3.1f, max: 100f)
cost(min: 0f, max: 999.99f)
}
String toString() { "${this.name} : ${this.city}, ${this.state}" }
}
I want to test the custom validation of startDateTime field. Test looks like that:
class RaceTests extends GrailsUnitTestCase {
protected void setUp() {
super.setUp()
}
protected void tearDown() {
super.tearDown()
}
void testCustomDateValidation() {
def race = new Race()
race.startDateTime = null
assertFalse(race.validate())
}
}
Test looks similar to the one from book I mentioned earlier. But I'm getting
groovy.lang.MissingMethodException: No signature of method: racetrack.Race.validate() is applicable for argument types: () values: []
I'm stuck and didn't find any solution :/ Any help will be appreciated.

You're missing the mockForConstraintsTests() call. The common pattern is to do this in setUp()
protected void setUp() {
super.setUp()
mockForConstraintsTests(Race)
}
For details: http://mrhaki.blogspot.com/2009/04/unit-testing-constraints-in-domain.html

You should not use unit tests or mocking to test domain classes. Grails does create a unit test for domain classes and this should be changed. Move the class to the same package and folder under test/integration and change the base class to GroovyTestCase and you'll have a proper test that runs with the in-memory database and tests persistence, not the mocking framework.

Related

Grails fail at test -app

I have my domain Class and my groovy class for unit test
class Product {
String product_code
String store
int price
String notes
//static hasOne = [description: Description]
static constraints = {
product_code blank:false, size: 1..15
price blank:false, scale: 2
store blank:false, size: 1..40
notes blank:true , size: 1..150
}
}
import org.apache.jasper.compiler.Node.ParamsAction;
import grails.test.mixin.*
import org.junit.*
import org.pricer.model.Product;
/**
* See the API for {#link grails.test.mixin.domain.DomainClassUnitTestMixin} for usage instructions
*/
#TestFor(Product)
class ProductTests {
void testSomething() {
if (Product.hasErrors()){
fail "not pass"
}else
assert "Pass"
}
}
when i try tu run test-app my ProductTest.testSomething i get
No signature of method: org.pricer.model.Product.hasErrors() is applicable for argument types: () values: []
Possible solutions: hasErrors(), getErrors(),
setErrors(org.springframework.validation.Errors), clearErrors(), hashCode()
groovy.lang.MissingMethodException: No signature of method: org.pricer.model.Product.hasErrors() is applicable for argument types: () values: []
Possible solutions: hasErrors(), getErrors(), setErrors(org.springframework.validation.Errors), clearErrors(), hashCode()
at
org.grails.datastore.gorm.GormStaticApi.methodMissing(GormStaticApi.groovy:97)
at org.pricer.ProductTests.testSomething(ProductTests.groovy:20)
You didn't instantiate domain class Product in your test. Try f.e.:
void testSomething() {
def product = new Product()
if (product.hasErrors()){
//do something
}
}
hasErrors() is an instance method. When you call Product.hasErrors() you're calling a class/static method, which in this case, does not exist.
So, as majkelo said, you need a Product instance first. But, you also need to trigger domain class validation so that if there are errors, hasErrors() will report them.
def product = new Product(product_code: 'ABC', store: 'StackOverflow', price: 1000000)
product.validate() /* You can also call product.save() to validate. */
if(product.hasErrors()) {
/* Do your thing */
}
A short-cut
You can combine validation and error checking like this:
if(product.validate()) {
/* validation passed. */
} else {
/* validation failed. */
}

Error in mocking service invocation

The error was on integration test of a service. Yes, we're currently doing integration test on our service as a workaround since we cannot test the services via unit test due to userType mapping.
Regarding userType: Our Domain uses interface as properties. In addition findByinterface won't work on grails.
see below:
class Domain {
SomeProperty someProperty
String otherProperty
}
public interface SomeProperty {
//methods
}
enum Enum implements SomeProperty {
NAME,
OTHERNAME
//implementation of methods
}
class Domain can handle different kinds of enum implementing the SomeProperty inteface.
Database won't know what particular value should it saved so we go with the userType. below is the Domain with the mapping
class Domain {
SomeProperty someProperty
String otherProperty
}
static mapping = {
id generator: 'sequence', column: 'id', params: [sequence: 'domain_sequence']
someProperty type : userType, {
column name: "someProperty", sqlType: "varchar", length: 255
}
}
Updated:
Code being tested:
class ServiceBeingTested {
AnotherServiceInsideServiceBeingTested anotherServiceInsideServiceBeingTested //or def anotherServiceInsideServiceBeingTested
public void methodBeingTested(param1, param2, param3) {
Object object = privateMethod(..., ...) //private method contains no other service calls. just pure logic
anotherServiceInsideServiceBeingTested.voidMethod1(....)
anotherServiceInsideServiceBeingTested.voidMethod2(....)
}
}
My integration test:
class ServiceBeingTestedIntegrationSpec extends IntegrationSpec {
ServiceBeingTested serviceBeingTested = new ServiceBeingTested()
AnotherServiceInsideServiceBeingTested anotherServiceInsideServiceBeingTested
void setUp () {
anotherServiceInsideServiceBeingTested = Mock()
serviceBeingTested.anotherServiceInsideServiceBeingTested = anotherServiceInsideServiceBeingTested
}
void cleanup () {
//code here
}
void "methodBeingTested should invoke the 2 service method call"() {
given:
//initialize data here
//code
when:
serviceBeingTested.methodBeingTested(param1, param2, param3)
then:
1 * anotherServiceInsideServiceBeingTested.voidMethod1(....)
1 * anotherServiceInsideServiceBeingTested.voidMethod2(....)
}
}
StackTrace:
| Too few invocations for:
1 * anotherServiceInsideServiceBeingTested.voidMethod2(....) (0 invocations)
Unmatched invocations (ordered by similarity):
1 * anotherServiceInsideServiceBeingTested.this$3$voidMethod2(....)
The other service call with same anotherServiceInsideServiceBeingTested -i.e anotherServiceInsideServiceBeingTested.voidMethod1 was correctly invoked. I tried changing the parameters in test for voidMethod2 to wildcards but will still result to this error.
Thanks
In integration test cases, you don't need to mock any of the services as these are available by default as the full application is loaded.
Though, in unit test, you may use #TestFor annotation and specify the service which you want to test, this will inject a service variable which would be available through you specification / test class.
#TestFor(Service)
class ServiceBeingTestedIntegrationSpec extends Specification {
ServiceBeingTested serviceBeingTested = new ServiceBeingTested()
void setUp () {
serviceBeingTested.service = service
}
void cleanup () {
//code here
}
void "methodBeingTested should invoke the 2 service method call"() {
given:
//initialize data here
//code
when:
serviceBeingTested.methodBeingTested(param1, param2, param3)
then:
1 * service.voidMethod1(....)
1 * service.voidMethod2(....)
}
}
Also, in case above you could also try to stub your methods in setup after mocking these to avoid any other service called by your service which will throw NullPointerException and eventually your test would fail in case of unit tests.
example of stubbing could be:
1*service.voidMethod1 >> {
//some code setting up any data for you.
}
1*service.voidMethod2 >> {
//some code setting up any data for you.
}
If you use stubbing like above you can simply write down unit test cases as well.

Inheritance in Grails Spock Integration Test

I have a few tests that are very similar in my Grails integration test suite (which uses Spock). I want to have a base test class which has the 90% of the common logic of the tests and then let test classes extend from it.
I was thinking:
public abstract BaseSpecification extends IntegrationSpec {
public baseTest() {
//
setUp:
//
...
when:
//
...
then:
...
}
}
and then:
public class SpecificTestSpecification extends BaseSpecification {
public baseTest() {
setup:
// more set up
super.baseTest();
when:
// some more specific testing
then:
// som more testing
}
}
But trying this I get two issues:
It runs both BaseClass and SpecificationClass
When the SpecificationClass runs, it fails on:
groovy.lang.MissingMethodException: No signature of method: BaseSpecification.baseTest() is applicable for argument types: () values: []
Possible solutions: any(), old(java.lang.Object), any(groovy.lang.Closure), notify(), wait(), Spy()
at
Any ideas how I can achieve inheritance in my spock integration tests?
I don't know if it can be done with Spock. When I tried I couln't find a way to reuse spock statements and what I did was to write a BaseSpecification class with utility methods that can be used inside spock statements.
This is an example test.
#TestFor(Address)
class AddressSpec extends BaseSpecification {
...
void "Country code should be 3 chars length"(){
when:
domain.countryCode = countryCode
then:
validateField('countryCode', isValid, 'minSize.notmet')
where:
[countryCode, isValid] << getMinSizeParams(3)
}
And the BaseSpecification class
class BaseSpecification extends Specification {
// Return params that can be asigned in `where` statement
def getMinSizeParams(Integer size){[
[RandomStringUtils.randomAlphabetic(size - 1), false],
[RandomStringUtils.randomAlphabetic(size), true]
]}
// Make an assetion, so it can be used inside `then` statement
protected void validateField(String field, String code, Boolean shouldBeValid){
domain.validate([field])
if(shouldBeValid)
assert domain.errors[field]?.code != code
else
assert domain.errors[field]?.code == code
}
}
It's an unit test but I think it should work with Integration tests too.
Okay, now I got your point.
You can pretty much use like this:
class BaseSpecification extends IntegrationSpec {
//User userInstance
def setup() {
// do your common stuff here like initialize a common user which is used everywhere
}
def cleanup() {
}
}
class SpecificTestSpecification extends BaseSpecification {
def setup() {
// specific setup here. Will call the super setup automatically
}
def cleanup() {
}
void "test something now"() {
// You can use that userInstance from super class here if defined.
}
}

one-to-many mapping in grails throwing exception

I am new to Groovy & Grails. I am working on one of the sample one-to-many relationship in Grails.
The below is the code.
class User {
//properties
String login
String password
String role
//constraints and order of display of fields in UI
static constraints = {
login(blank: false, nullable: false, unique: true)
password(blank: false, nullable: false, password: true)
role(inList:["Admin", "Member"])
}
static hasMany = [posts : Post]
}
class Post {
String content
Date dateCreated
static constraints = {
content(blank: true)
}
static belongsTo = [user : User]
}
My Test class in Groovy
#TestFor(User)
class UserTests {
void testUserToPost() {
def user = new User(login: "joe", password: "joe", role:"Admin")
user.addToPosts(new Post(content: "First"));
user.addToPosts(new Post(content: "Second"));
user.addToPosts(new Post(content: "Third"));
user.save(flush: true)
assertEquals 3, User.get(user.id).posts.size()
}
}
While running the test class, getting following exception:
groovy.lang.MissingMethodException: No signature of method: com.library.forum.User.addToPosts() is applicable for argument types: (com.library.forum.Post) values: [com.library.forum.Post : (unsaved)]
Possible solutions: getPosts() at com.library.forum.UserTests.testUserToPost(UserTests.groovy:17)
Can anyone tell me where is the problem in code.
Since Grails 2.1.4, there's a change in mock behavior because of performance issue. So you need to mock all associated entities of the mocked entity.
See GRAILS-9637 - Due to a performance issue, #Mock no longer mocks
associated entities of the mocked entity. These have to be manually
specified. For example the following test will fail in 2.1.4 and
above:
#Mock(Author)
void testAddToBooks() {
def a = new Author()
a.addToBooks(new Book())
}
To correct the above test you need to mock both Author and Book:
#Mock([Author, Book])
void testAddToBooks() {
def a = new Author()
a.addToBooks(new Book())
}
You can check this reference.
You need to mock all related domain classes. Change :
#TestFor(User)
class UserTests {
to
#TestFor(User)
#Mock(Post)
class UserTests {
If you need, the mock annotation support a list of classes, for example: #Mock([Domain1, Domain2, Domain3])

Mixins on Grails Domains Classes broken in upgrade to 2.2

We have a multiple Grails 2.0.3 domain classes that use the #Mixin annotation
like so:
#Mixin(PremisesMixin)
class Clinic {
Premises premises
String name
....
It works really well!
In trying to update to 2.2.2 the mixins don't seem to work. We use the fixtures plugin to bootstrap some data, and in the process of starting up we get errors related to the getters and setters that should injected by the mixins not being present.
I did Find there there are some issues with groovy mixins in more recent versions of grails, but there is a Grails specific replacement http://jira.grails.org/browse/GRAILS-9901
but changing to
#grails.util.Mixin(PremisesMixin)
class Clinic { ...
gives other errors.
Getter for property 'fax' threw exception; nested exception is java.lang.reflect.InvocationTargetException
So is there a way to utilize mixins on Grails domain classes in the latest version of grails or do I need to refactor my code to avoid them?
update:
the premises mixin which is in src/groovy looks like this:
class PremisesMixin implements Serializable {
private static final long serialVersionUID = 1L
static fields = ['addressLine1', 'addressLine2', 'city', 'county', 'state', 'postalCode', 'plus4', 'phone', 'latitude', 'longitude']
String getAddressLine1() { premises?.addressLine1 }
void setAddressLine1(String addressLine1) { premises?.addressLine1 = addressLine1 }
String getAddressLine2() { premises?.addressLine2 }
void setAddressLine2(String addressLine2) { premises?.addressLine2 = addressLine2 }
String getCity() { premises?.city }
void setCity(String city) { premises?.city = city }
...
String getPhone() { premises?.phone }
void setPhone(String phone) { premises?.phone = phone }
String getFax() { premises?.fax }
void setFax(String fax) { premises?.fax = fax }
...
// Workaround for open Groovy bug with Mixins https://issues.apache.org/jira/browse/GROOVY-3612
String toString() {
this as String
}
}
and Premises looks like this:
class Premises {
String addressLine1
String addressLine2
String city
String state
...
String county
String phone
String fax
Double latitude
Double longitude
}
It works for me in Grails 2.2.2 with the below setup:
#grails.util.Mixin(PremisesMixin)
class Clinic {
String name
static constraints = {
}
}
class Premises {
String fax
static constraints = {
fax nullable: true
}
}
class PremisesMixin {
//Without this a runtime error is thrown,
//like property 'premises' not found in Clinic.
Premises premises
void setFax(String fax) {
premises?.fax = fax
}
String getFax() {
premises?.fax
}
}
//Test Case
def clinic = new Clinic(name: "TestClinic")
clinic.premises = new Premises().save(flush: true, failOnError: true)
clinic.fax = "123456"
clinic.save(flush: true, failOnError: true)
Clinic.list().each{assert it.fax == '123456'}
Premises.list().each{assert it.fax == '123456'}
The logic for Mixin transformation has not been modified for 2.2.x version although I see modifications done on it in master branch, but the change is minute(generic class literals used).
Few Questions:
1. How was premises accessible in the mixin class? I do not see where it is defined in the Mixin class.
2. Actually when were you facing the error, run-app or during creation of Clinic(similar to what is done in test above)?

Resources