3
votes

Update 3 - Passing and failing examples

SO won't let me paste them in because of the length of this post, so examples can be found here: http://pastebin.com/gMVa4Gd1

Update #2. Based on the proposed answer regarding updating the scaffolding plugin version, I've rerun the generate-all '*' command and the test. Here's the updated output, with the generated code beneath it:

| Error Forked Grails VM exited with error
| Environment set to development.....
| Running without daemon...
| Running 2 unit tests...
| Running 2 unit tests... 1 of 2
| Running 2 unit tests... 2 of 2
| Running 2 unit tests... 3 of 3
| Failure:  Test the save action correctly persists an instance(AddressControllerSpec)
|  org.codehaus.groovy.grails.web.servlet.mvc.exceptions.CannotRedirectException: Cannot redirect for object [Address : (unsaved)] it is not a domain or has no identifier. Use an explicit redirect instead 
    at AddressController.tt__save_closure9_closure14(AddressController.groovy:43)
    at AddressController.$tt__save(AddressController.groovy:40)
    at AddressControllerSpec.Test the save action correctly persists an instance(AddressControllerSpec.groovy:54)
| Running 2 unit tests... 4 of 4
| Running 2 unit tests... 5 of 5
| Running 2 unit tests... 6 of 6
| Error 2014-06-06 09:39:45,246 [main] ERROR mvc.GrailsParameterMap  - Error processing form encoded PUT request
Message: null
   Line | Method
->>  98 | doCall                    in AddressController$_notFound_closure8_closure12
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    96 | notFound                  in AddressController
|    56 | $tt__update . . . . . . . in     ''
|    98 | $spock_feature_0_5        in AddressControllerSpec
|   138 | invokeMethod . . . . . .  in org.spockframework.util.ReflectionUtil
|   330 | invokeRaw                 in org.spockframework.runtime.BaseSpecRunner
|   311 | invoke . . . . . . . . .  in     ''
|   285 | invokeFeatureMethod       in     ''
|   256 | doRunIteration . . . . .  in     ''
|   138 | invokeMethod              in org.spockframework.util.ReflectionUtil
|    91 | invokeTargetMethod . . .  in org.spockframework.runtime.extension.MethodInvocation
|    85 | proceed                   in     ''
|    37 | evaluate . . . . . . . .  in org.spockframework.runtime.extension.builtin.AbstractRuleInterceptor$1
|    48 | evaluate                  in grails.test.runtime.TestRuntimeJunitAdapter$1$2
|    38 | intercept . . . . . . . . in org.spockframework.runtime.extension.builtin.TestRuleInterceptor
|    84 | proceed                   in org.spockframework.runtime.extension.MethodInvocation
|   319 | invoke . . . . . . . . .  in org.spockframework.runtime.BaseSpecRunner
|   223 | runIteration              in     ''
|   214 | initializeAndRunIteration in     ''
|   205 | runSimpleFeature          in     ''
|   199 | doRunFeature . . . . . .  in     ''
|   138 | invokeMethod              in org.spockframework.util.ReflectionUtil
|   330 | invokeRaw . . . . . . . . in org.spockframework.runtime.BaseSpecRunner
|   311 | invoke                    in     ''
|   175 | runFeature . . . . . . .  in     ''
|   152 | runFeatures               in     ''
|   112 | doRunSpec . . . . . . . . in     ''
|   138 | invokeMethod              in org.spockframework.util.ReflectionUtil
|    91 | invokeTargetMethod . . .  in org.spockframework.runtime.extension.MethodInvocation
|    85 | proceed                   in     ''
|    37 | evaluate . . . . . . . .  in org.spockframework.runtime.extension.builtin.AbstractRuleInterceptor$1
|    74 | evaluate                  in grails.test.runtime.TestRuntimeJunitAdapter$3$4
|    38 | intercept . . . . . . . . in org.spockframework.runtime.extension.builtin.ClassRuleInterceptor
|    84 | proceed                   in org.spockframework.runtime.extension.MethodInvocation
|   319 | invoke . . . . . . . . .  in org.spockframework.runtime.BaseSpecRunner
|    91 | runSpec                   in     ''
|    82 | run . . . . . . . . . . . in     ''
|    63 | run                       in org.spockframework.runtime.Sputnik
|   127 | runChild . . . . . . . .  in org.junit.runners.Suite
|    26 | runChild                  in     ''
|   238 | run . . . . . . . . . . . in org.junit.runners.ParentRunner$3
|    63 | schedule                  in org.junit.runners.ParentRunner$1
|   236 | runChildren . . . . . . . in org.junit.runners.ParentRunner
|    53 | access$000                in     ''
|   229 | evaluate . . . . . . . .  in org.junit.runners.ParentRunner$2
|   309 | run                       in org.junit.runners.ParentRunner
|   160 | run . . . . . . . . . . . in org.junit.runner.JUnitCore
^   138 | run                       in     ''
| Failure:  Test the update action performs an update on a valid domain instance(AddressControllerSpec)
|  java.lang.NullPointerException: Cannot get property 'id' on null object
    at AddressControllerSpec.Test the update action performs an update on a valid domain instance(AddressControllerSpec.groovy:122)
| Running 2 unit tests... 7 of 7
| Failure:  Test that the delete action deletes an instance if it exists(AddressControllerSpec)
|  Condition not satisfied:
Address.count() == 1
        |       |
        0       false
    at AddressControllerSpec.Test that the delete action deletes an instance if it exists(AddressControllerSpec.groovy:142)
| Completed 7 unit tests, 3 failed in 0m 10s
Configuring Spring Security Core ...
... finished configuring Spring Security Core
| Tests FAILED 
| Error Forked Grails VM exited with error

/************************************* AddressControllerSpec ********************************************/



import grails.test.mixin.*
import spock.lang.*

@TestFor(AddressController)
@Mock(Address)
class AddressControllerSpec extends Specification {

    def populateValidParams(params) {
        assert params != null
        // TODO: Populate valid properties like...
        //params["name"] = 'someValidName'
    }

    void "Test the index action returns the correct model"() {

        when:"The index action is executed"
            controller.index()

        then:"The model is correct"
            !model.addressInstanceList
            model.addressInstanceCount == 0
    }

    void "Test the create action returns the correct model"() {
        when:"The create action is executed"
            controller.create()

        then:"The model is correctly created"
            model.addressInstance!= null
    }

    void "Test the save action correctly persists an instance"() {

        when:"The save action is executed with an invalid instance"
            request.contentType = FORM_CONTENT_TYPE
            request.method = 'POST'
            def address = new Address()
            address.validate()
            controller.save(address)

        then:"The create view is rendered again with the correct model"
            model.addressInstance!= null
            view == 'create'

        when:"The save action is executed with a valid instance"
            response.reset()
            populateValidParams(params)
            address = new Address(params)

            controller.save(address)

        then:"A redirect is issued to the show action"
            response.redirectedUrl == '/address/show/1'
            controller.flash.message != null
            Address.count() == 1
    }

    void "Test that the show action returns the correct model"() {
        when:"The show action is executed with a null domain"
            controller.show(null)

        then:"A 404 error is returned"
            response.status == 404

        when:"A domain instance is passed to the show action"
            populateValidParams(params)
            def address = new Address(params)
            controller.show(address)

        then:"A model is populated containing the domain instance"
            model.addressInstance == address
    }

    void "Test that the edit action returns the correct model"() {
        when:"The edit action is executed with a null domain"
            controller.edit(null)

        then:"A 404 error is returned"
            response.status == 404

        when:"A domain instance is passed to the edit action"
            populateValidParams(params)
            def address = new Address(params)
            controller.edit(address)

        then:"A model is populated containing the domain instance"
            model.addressInstance == address
    }

    void "Test the update action performs an update on a valid domain instance"() {
        when:"Update is called for a domain instance that doesn't exist"
            request.contentType = FORM_CONTENT_TYPE
            request.method = 'PUT'
            controller.update(null)

        then:"A 404 error is returned"
            response.redirectedUrl == '/address/index'
            flash.message != null


        when:"An invalid domain instance is passed to the update action"
            response.reset()
            def address = new Address()
            address.validate()
            controller.update(address)

        then:"The edit view is rendered again with the invalid instance"
            view == 'edit'
            model.addressInstance == address

        when:"A valid domain instance is passed to the update action"
            response.reset()
            populateValidParams(params)
            address = new Address(params).save(flush: true)
            controller.update(address)

        then:"A redirect is issues to the show action"
            response.redirectedUrl == "/address/show/$address.id"
            flash.message != null
    }

    void "Test that the delete action deletes an instance if it exists"() {
        when:"The delete action is called for a null instance"
            request.contentType = FORM_CONTENT_TYPE
            request.method = 'DELETE'
            controller.delete(null)

        then:"A 404 is returned"
            response.redirectedUrl == '/address/index'
            flash.message != null

        when:"A domain instance is created"
            response.reset()
            populateValidParams(params)
            def address = new Address(params).save(flush: true)

        then:"It exists"
            Address.count() == 1

        when:"The domain instance is passed to the delete action"
            controller.delete(address)

        then:"The instance is deleted"
            Address.count() == 0
            response.redirectedUrl == '/address/index'
            flash.message != null
    }
}


/******************************** AddressController ******************************************/





import static org.springframework.http.HttpStatus.*
import grails.transaction.Transactional

@Transactional(readOnly = true)
class AddressController {

    static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond Address.list(params), model:[addressInstanceCount: Address.count()]
    }

    def show(Address addressInstance) {
        respond addressInstance
    }

    def create() {
        respond new Address(params)
    }

    @Transactional
    def save(Address addressInstance) {
        if (addressInstance == null) {
            notFound()
            return
        }

        if (addressInstance.hasErrors()) {
            respond addressInstance.errors, view:'create'
            return
        }

        addressInstance.save flush:true

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.created.message', args: [message(code: 'address.label', default: 'Address'), addressInstance.id])
                redirect addressInstance
            }
            '*' { respond addressInstance, [status: CREATED] }
        }
    }

    def edit(Address addressInstance) {
        respond addressInstance
    }

    @Transactional
    def update(Address addressInstance) {
        if (addressInstance == null) {
            notFound()
            return
        }

        if (addressInstance.hasErrors()) {
            respond addressInstance.errors, view:'edit'
            return
        }

        addressInstance.save flush:true

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.updated.message', args: [message(code: 'Address.label', default: 'Address'), addressInstance.id])
                redirect addressInstance
            }
            '*'{ respond addressInstance, [status: OK] }
        }
    }

    @Transactional
    def delete(Address addressInstance) {

        if (addressInstance == null) {
            notFound()
            return
        }

        addressInstance.delete flush:true

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.deleted.message', args: [message(code: 'Address.label', default: 'Address'), addressInstance.id])
                redirect action:"index", method:"GET"
            }
            '*'{ render status: NO_CONTENT }
        }
    }

    protected void notFound() {
        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.not.found.message', args: [message(code: 'address.label', default: 'Address'), params.id])
                redirect action: "index", method: "GET"
            }
            '*'{ render status: NOT_FOUND }
        }
    }
}

Update

This appears to happen only when testing in Intellij. At least...only 3 of the 7 tests fail when run from the command line. Here're the results from a command line run:

test-app .AddressControllerSpec
| Running 2 unit tests... 3 of 3
| Failure:  Test the save action correctly persists an instance(.AddressControllerSpec)
|  Condition not satisfied:
model.addressInstance!= null
|     |              |
[:]   null           false
    at .AddressControllerSpec.Test the save action correctly persists an instance(AddressControllerSpec.groovy:48)
| Running 2 unit tests... 6 of 6
| Failure:  Test the update action performs an update on a valid domain instance(.AddressControllerSpec)
|  Condition not satisfied:
response.redirectedUrl == '/address/index'
|        |             |
|        null          false
org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletResponse@2906a2bf
    at .AddressControllerSpec.Test the update action performs an update on a valid domain instance(AddressControllerSpec.groovy:102)
| Running 2 unit tests... 7 of 7
| Failure:  Test that the delete action deletes an instance if it exists(.AddressControllerSpec)
|  Condition not satisfied:
response.redirectedUrl == '/address/index'
|        |             |
|        null          false
org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletResponse@590bd021
    at .AddressControllerSpec.Test that the delete action deletes an instance if it exists(AddressControllerSpec.groovy:133)
| Completed 7 unit tests, 3 failed in 0m 6s
Configuring Spring Security Core ...
... finished configuring Spring Security Core
| Tests FAILED 

In Grails 2.4.0, I've used "grails generate-all '*'" to generate scaffolding for my application. This also creates controller tests. However, I believe these controller tests are wrong. In my case, 6 out of 7 tests fail every time. Here's an example of a broken one:

@TestFor(AddressController)
@Mock(Address)
class AddressControllerSpec extends Specification {
.....
void "Test the create action returns the correct model"() {
        when:"The create action is executed"
            controller.create()

        then:"The model is correctly created"
            model.addressInstance != null
    }

When this executes, it fails, and the output is:

Condition not satisfied:

model.addressInstance != null
|     |               |
|     null            false
[address:Address : (unsaved)]

    at AddressControllerSpec.Test the create action returns the correct model(AddressControllerSpec.groovy:36)

Note that the model itself is an Address object, but addressInstance is null.

Looking at the associated controller action in generated AddressController.groovy:

def create() {
    respond new Address(params)
}

Seeing how this doesn't set a variable called addressInstance, isn't this obviously why this test is failing? I'm new to Grails (well, new to trying to do something with it), so I'm not sure if there's some magic which is supposed to be at play here that isn't, but the fact that the model is indeed being set to the object returned by the action would seem to me to indicate a bug.

Can someone please check my reasoning here? Am I wrong about this code being in correct?

The only test which passes for me is: "Test the index action returns the correct model"

For reference, here's the full controller and test code:

import static org.springframework.http.HttpStatus.*
import grails.transaction.Transactional

@Transactional(readOnly = true)
class AddressController {

    static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond Address.list(params), model:[addressInstanceCount: Address.count()]
    }

    def show(Address addressInstance) {
        respond addressInstance
    }

    def create() {
        respond new Address(params)
    }

    @Transactional
    def save(Address addressInstance) {
        if (addressInstance == null) {
            notFound()
            return
        }

        if (addressInstance.hasErrors()) {
            respond addressInstance.errors, view:'create'
            return
        }

        addressInstance.save flush:true

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.created.message', args: [message(code: 'address.label', default: 'Address'), addressInstance.id])
                redirect addressInstance
            }
            '*' { respond addressInstance, [status: CREATED] }
        }
    }

    def edit(Address addressInstance) {
        respond addressInstance
    }

    @Transactional
    def update(Address addressInstance) {
        if (addressInstance == null) {
            notFound()
            return
        }

        if (addressInstance.hasErrors()) {
            respond addressInstance.errors, view:'edit'
            return
        }

        addressInstance.save flush:true

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.updated.message', args: [message(code: 'Address.label', default: 'Address'), addressInstance.id])
                redirect addressInstance
            }
            '*'{ respond addressInstance, [status: OK] }
        }
    }

    @Transactional
    def delete(Address addressInstance) {

        if (addressInstance == null) {
            notFound()
            return
        }

        addressInstance.delete flush:true

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.deleted.message', args: [message(code: 'Address.label', default: 'Address'), addressInstance.id])
                redirect action:"index", method:"GET"
            }
            '*'{ render status: NO_CONTENT }
        }
    }

    protected void notFound() {
        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.not.found.message', args: [message(code: 'address.label', default: 'Address'), params.id])
                redirect action: "index", method: "GET"
            }
            '*'{ render status: NOT_FOUND }
        }
    }
}


/*********************************************************************************/
import grails.test.mixin.*
import spock.lang.*


@TestFor(AddressController)
@Mock(Address)
class AddressControllerSpec extends Specification {
    def log = LogFactory.getLog(getClass())
    def populateValidParams(params) {
        assert params != null
        // TODO: Populate valid properties like...
        //params["name"] = 'someValidName'
    }

    void "Test the index action returns the correct model"() {

        when:"The index action is executed"

            controller.index()

        then:"The model is correct"
            !model.addressInstanceList
            model.addressInstanceCount == 0
    }

    void "Test the create action returns the correct model"() {
        when:"The create action is executed"
            controller.create()

        then:"The model is correctly created"
            model.addressInstance != null
    }

    void "Test the save action correctly persists an instance"() {

        when:"The save action is executed with an invalid instance"
            request.contentType = FORM_CONTENT_TYPE
            def address = new Address()
            address.validate()
            controller.save(address)

        then:"The create view is rendered again with the correct model"
            //model.addressInstance!= null
            view == 'create'

        when:"The save action is executed with a valid instance"
            response.reset()
            populateValidParams(params)
            address = new Address(params)

            controller.save(address)

        then:"A redirect is issued to the show action"
            response.redirectedUrl == '/address/show/1'
            controller.flash.message != null
            Address.count() == 1
    }

    void "Test that the show action returns the correct model"() {
        when:"The show action is executed with a null domain"
            controller.show(null)

        then:"A 404 error is returned"
            response.status == 404

        when:"A domain instance is passed to the show action"
            populateValidParams(params)
            def address = new Address(params)
            controller.show(address)

        then:"A model is populated containing the domain instance"
            model.addressInstance == address
    }

    void "Test that the edit action returns the correct model"() {
        when:"The edit action is executed with a null domain"
            controller.edit(null)

        then:"A 404 error is returned"
            response.status == 404

        when:"A domain instance is passed to the edit action"
            populateValidParams(params)
            def address = new Address(params)
            controller.edit(address)

        then:"A model is populated containing the domain instance"
            model.addressInstance == address
    }

    void "Test the update action performs an update on a valid domain instance"() {
        when:"Update is called for a domain instance that doesn't exist"
            request.contentType = FORM_CONTENT_TYPE
            controller.update(null)

        then:"A 404 error is returned"
            response.redirectedUrl == '/address/index'
            flash.message != null


        when:"An invalid domain instance is passed to the update action"
            response.reset()
            def address = new Address()
            address.validate()
            controller.update(address)

        then:"The edit view is rendered again with the invalid instance"
            view == 'edit'
            model.addressInstance == address

        when:"A valid domain instance is passed to the update action"
            response.reset()
            populateValidParams(params)
            address = new Address(params).save(flush: true)
            controller.update(address)

        then:"A redirect is issues to the show action"
            response.redirectedUrl == "/address/show/$address.id"
            flash.message != null
    }

    void "Test that the delete action deletes an instance if it exists"() {
        when:"The delete action is called for a null instance"
            request.contentType = FORM_CONTENT_TYPE
            controller.delete(null)

        then:"A 404 is returned"
            response.redirectedUrl == '/address/index'
            flash.message != null

        when:"A domain instance is created"
            response.reset()
            populateValidParams(params)
            def address = new Address(params).save(flush: true)

        then:"It exists"
            Address.count() == 1

        when:"The domain instance is passed to the delete action"
            controller.delete(address)

        then:"The instance is deleted"
            Address.count() == 0
            response.redirectedUrl == '/address/index'
            flash.message != null
    }
}
1
Did you solved this problem ? I'm facing the same: generated tests are not passing. I actually added the right request.method as Jeff Scott answer pointed out, but the tests don't pass neither... - lmo

1 Answers

3
votes

You need to specify the request method when testing controller actions which are restricted by allowedMethods.

See https://jira.grails.org/browse/GPSCAFFOLD-95

https://jira.grails.org/browse/GRAILS-8426

https://github.com/grails-plugins/grails-scaffolding/commit/db4eed57449e56225821ab565229b76bc394d2be

You can update your scaffolding plugin in BuildConfig.groovy to version 2.1.1 or you can update the generated tests manually.

I hope that helps.