更新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
}
}
答案 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版,也可以手动更新生成的测试。
我希望有所帮助。