Search code examples
unit-testingvalidationgrailsgrails-orm

Grails Mocks: Decoupling Validation from Controller


I have a several grails controllers that I generated and modified slightly. I'm working with the generated unit tests and getting them to pass, but I think I'm doing it the hard way. This is what I have.

package edu.liberty.swiper

import grails.test.mixin.*

import org.junit.*

@TestFor(AttendanceController)
@Mock([Attendance, Location, Reason, Person, LocCapMode, GuestContactMode, UserAccount])
class AttendanceControllerTests {

def location
def reason

void setUp() {
    def capMode = new LocCapMode(description: "loc cap mode", username: "testuser").save(failOnError: true)
    def guestMode = new GuestContactMode(description: "Guest Contact Mode", username: "testuser").save(failOnError: true)
    location = new Location(description: "foo", locCapMode: capMode, username: "testuser", guestContactMode: guestMode).save(failOnError: true)
    reason = new Reason(description: "test reason", username: "testuser").save(failOnError: true)
    def person = new Person(firstName: "John", lastName: "Smith", lid: "L12345678", mi: "Q", pidm: 12345).save(failOnError: true)
    def userAccount = new UserAccount(pidm: 12345, username: "testuser").save(failOnError:true)
}

def populateValidParams(params) {
    assert params != null
    params.personId = '12345'
    params.username = "testuser"
    params["location.id"] = location.id
    params["reason.id"] = reason.id
    params.timeIn = new Date()
}

void testIndex() {
    ...
}

void testList() {
    ...
}

void testCreate() {
    ...
}

void testSave() {
    controller.save()

    assert model.attendanceInstance != null
    assert view == '/attendance/create'

    response.reset()

    populateValidParams(params)
    controller.save()

    assert response.redirectedUrl == '/attendance/show/1'
    assert controller.flash.message != null
    assert Attendance.count() == 1
}

void testEdit() {
    ...
}

...

What I'm hoping for is the ability to dynamically mock domain object, i.e. expect(Attendance.save()).andReturn(null) or expect(Attendance.save()).andReturn(testAttendance), so that I don't have to create the web of associated objects in my setUp method that are necessary to validate the domain object that is being manipulated by the controller.

Am I just looking at this all wrong? It seems like I should be able to decouple the controller logic from the validation logic., so that I can just tell the mock to tell the controller that validation passed or failed. Thanks in advance.


Solution

  • I don't think there is a way to tell the mock that the validation of a certain object that is handled by a controller passed or failed but I might be wrong. But as I understand it your main concern is the creation of the web of associated objects right?

    Without knowing what your controller looks like I would guess that you are getting needed domain objects in your controller by ID (e.g. Location) and load a Person by pidm and so on.

    To simplify the creation of needed domain objects you could use .save(validate: false). Your setUp method could look like this:

    location = new Location().save(validate: false)
    reason = new Reason().save(validate: false)
    

    If you only need objects with valid IDs this would be sufficient.

    new Person(pidm: 12345).save(validate: false)
    new UserAccount(username: "testuser").save(validate: false)
    

    Set certain fields to be able to use a finder like UserAccount.findByUserName().

    So if your controller does something like

    location = Location.get(params["location.id"])
    reason = Reason.get(params["reason.id"])
    userAccount = UserAccount.findByUserName(params.username)
    ...
    new Attendance(location: location, reason: reason, userAccount: userAccount, ...)
    

    the aforementioned lines should be satisfactory for your setUp method.

    .save(validate: false) is very useful to just set values that are really needed in your test. I hope I got the whole thing right and I could be of help.