Ru Ru
Ru Ru

Reputation: 165

Failing to save during Test on Controller

I have been struggling with this for quite sometime and it seems like a common problem, with no solution that worked for me.

What I am trying to do is test the controller which calls a save(), and inside the save() the method calls a service to save the employee with the given command objects.

This is how the code looks like:

 def save(EmployeeInputCommand employeeCommand, AddressCommand addressCommand) { 
    if (addressCommand.address.validate()) {
        println "Validated address input"
        employeeManagerService.save(employeeCommand, addressCommand)
        println "Employee should be saved"
        println "------------------------->"
        println "${employeeGetterService.each {errors}}"
        flash.message = "Employee has been successfully created!"
        redirect(action: "index")
    } else {
        println "Addrescommand didnt validate: ${addressCommand.errors}"
        flash.message = "Please input valid data!!!"
        redirect(action: "create", model: [employee: employeeCommand, address: addressCommand])
    }
}

The service contains this:

def save(EmployeeInputCommand employeeCommand, AddressCommand addressCommand) {
    def employee = new Employee(employeeCommand.properties)
    employee.address = addressCommand.address
    for (id in employeeCommand.s) {
        employee.addToSkills(Skill.get(id))
    }
    if (employee.validate()) {
        employee.address.save()
        employee.save()
    }
    else {
       return false
    }
}

I know this works when I try to actually save the employee in my application, but during the Unit Test process nothing happens.

My Test:

     def "Create an Employee with good params"() {
    given: "A valid employee command object"
    def employeeCommand = new EmployeeInputCommand(
            name: "John",
            surname: "Smith",
            birthdate: Date.parse("yyyy-MM-dd", "1992-06-08"),
            salary: 21000,
            s: [1, 3, 5]
    )

    and: "Also a valid address command object"
    def addressCommand = new AddressCommand(
            street: "Green Alley",
            houseid: "42",
            city: "Odense",
            county: "Fyn",
            postCode: "5000 C"
    )

    expect:
    addressCommand.validate()
    !Employee.findByNameAndSurname("John", "Smith")

    when: "Saving employee"
    request.method = "POST"
    controller.save(employeeCommand, addressCommand)
    Employee employee = Employee.findByNameAndSurname("John", "Smith")

    println Employee.list()

    then: "The employee is created, and browser redirected"
    Employee.count == 4
    employee
    employee.skills.size() == 3
    employee.address.postCode == "5000 C"
}

The test is failing with a null error when the controller.save() is called. I have spend too much time trying to solve this, which has all been in vain

This is the output screenshot

Error output

Upvotes: 1

Views: 86

Answers (3)

BahmanM
BahmanM

Reputation: 1445

I don't believe testing a controller should cover the service logic. I'd mock the service, if I were to write a unit test for a controller, and test the service separately in its own unit/integration tests. For example:

/*
 * in controller spec
 */
def setup() {
  def serviceMock = new MockFor(EmployeeManagerService)
  serviceMock.ignore('save') { ec, ac ->
    Employee.buildWithoutSave().save(validate: false)
  }
  serviceMock.use {
    controller.employeeManagerService = new EmployeeManagerService()
  }
}

def 'testFoo'() {
  when:
  // ...
  controller.employeeManagerService.save()
  // ...
  then:
  // ...
}

Note that the code is using the excellent Build Test Data plugin.

Upvotes: 1

mohsenmadi
mohsenmadi

Reputation: 2377

You need to perform your tests as Integration Tests, not Unit Tests. Integration testing is needed when you're testing database-related logic (CRUD ops), not just mocking the CRUD ops which is what unit tests do.

Upvotes: 0

MKB
MKB

Reputation: 7619

I think the issue is with service in unit testing. In unit testing, you need defineBeans closure to use spring beans-

void "test the filter"() {
    setup:
    defineBeans {
        employeeManagerService(EmployeeManagerService) { bean ->
            bean.autowire = true
        }
    }

    when:
    ...

    then:
    ...
}

Ref# Inject Services in Grails Unit Test

Upvotes: 0

Related Questions