grails generate-all'*'创建破坏的Controller测试?脚手架的生成是否破裂?

时间:2014-06-05 22:09:26

标签: grails groovy

更新3 - 传递和失败的示例

因为这篇文章的篇幅很长,所以不允许我粘贴它们,所以可以在这里找到示例:http://pastebin.com/gMVa4Gd1

更新#2。基于有关更新脚手架插件版本的建议答案,我重新运行了generate-all'*'命令和测试。这是更新后的输出,其下面是生成的代码:

| 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 }
        }
    }
}

更新

这似乎只在Intellij中进行测试时才会发生。至少...从命令行运行时,7个测试中只有3个失败。以下是命令行运行的结果:

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 

在Grails 2.4.0中,我使用了“grails generate-all'*'”来为我的应用程序生成脚手架。这也会创建控制器测试。但是,我相信这些控制器测试是错误的。就我而言,7次测试中有6次每次都失败。这是一个破碎的例子:

@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
    }

执行此操作时,它会失败,输出为:

Condition not satisfied:

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

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

请注意,模型本身是一个Address对象,但addressInstance为null。

查看生成的AddressController.groovy中的关联控制器操作:

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

看看这怎么没有设置一个名为addressInstance的变量,这显然不是为什么这个测试失败了?我是Grails的新手(嗯,刚尝试用它来做事),所以我不确定是否有一些应该在这里发挥作用的魔法不是,但事实上这个模型确实是被设置为动作返回的对象在我看来是一个错误。

有人可以在这里查看我的推理吗?我错误地认为这段代码是正确的吗?

唯一通过的测试是:“测试索引操作返回正确的模型”

供参考,这是完整的控制器和测试代码:

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 个答案:

答案 0 :(得分:3)

在测试受allowedMethods限制的控制器操作时,您需要指定请求方法。

请参阅https://jira.grails.org/browse/GPSCAFFOLD-95

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

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

您可以将BuildConfig.groovy中的脚手架插件更新为2.1.1版,也可以手动更新生成的测试。

我希望有所帮助。