Grails 在控制器测试中创建的域对象不会在数据库中结束

Grails 在控制器测试中创建的域对象不会在数据库中结束,grails,controller,mocking,Grails,Controller,Mocking,我一直试图在行动中遵循圣杯(GiA MEAP,第二版),但偶尔我会遇到一些即使花了很多时间也无法解决的问题。这就是其中之一。很抱歉,这个问题与GiA中的示例有关(清单6.x,第144页中的v13,ch6,PostControllerSpec),但是我将尝试用一个精简但完整的代码来捕获这个问题。我有两个域模型,User和Post package grailstuts class User { String loginId static hasMany = [ posts: Pos

我一直试图在行动中遵循圣杯(GiA MEAP,第二版),但偶尔我会遇到一些即使花了很多时间也无法解决的问题。这就是其中之一。很抱歉,这个问题与GiA中的示例有关(清单6.x,第144页中的v13,ch6,PostControllerSpec),但是我将尝试用一个精简但完整的代码来捕获这个问题。我有两个域模型,
User
Post

package grailstuts

class User {
    String loginId
    static hasMany = [ posts: Post ]

    static constraints = { loginId(blank: false, unique: true) }
}

PostController
如下所示

package grailstuts

class PostController {
    static scaffold = true

    def timeline() {
        def user = User.findByLoginId(params.id)
        if (!user) {
            response.sendError(404)
        } else {
            [ user : user ]
        }
    }

    def addPost() {
        def user = User.findByLoginId(params.id)
        if (user) {
            def post = new Post(params)
            user.addToPosts(post)
            if (user.save()) {
                flash.message = "Successfully created Post"
            } else {
                flash.message = "Invalid or empty post"
            }
        } else {
            flash.message = "Invalid User Id"
        }
        redirect(action: 'timeline', id: params.id)
    }
}
当我尝试按如下方式对控制器进行单元测试时(如书中所示),我遇到了这个问题

package grailstuts

import grails.test.mixin.Mock
import grails.test.mixin.TestFor
import spock.lang.Specification

@TestFor(PostController)
@Mock([User,Post])
class PostControllerSpec extends Specification {

    def "Adding a valid new post to the timeline"() {
        given: "A user with posts in the db"
        User chuck = new User(loginId: "chuck_norris").save(failOnError: true)

        and: "A loginId parameter"
        params.id = chuck.loginId

        and: "Some content for the post"
        params.content = "Chuck Norris can unit test entire applications with a single assert."

        when: "addPost is invoked"
        def model = controller.addPost()

        then: "our flash message and redirect confirms the success"
        flash.message == "Successfully created Post"
        response.redirectedUrl == "/post/timeline/${chuck.loginId}"
        Post.countByUser(chuck) == 1
    }
}
该测试通过了前两个测试
flash.message==“成功创建了Post”
response.redirectedUrl==“Post/timeline/${chuck.loginId}”
,但在最后一行
Post.countByUser(chuck)==1
(这非常令人费解,无法解释失败的原因)。我得到以下信息:

|  Condition not satisfied:
Post.countByUser(chuck) == 1
     |           |      |
     0           |      false
                 grailstuts.User : 1

我的问题是,为什么上面的测试失败了,即使它成功地创建了post?我花了相当多的时间试图找出错误,但还没有运气。

在无数次尝试之后,我做了一些有效的事情。不过,我还是很困惑,如果有人能解释一下,那真的会有帮助。我为它工作所做的唯一更改是,如果
user.save()
true
,则显式保存
post
。这是修改后的
addPost()
(唯一更改的行清楚地标记为
//显式保存帖子!!
,其他地方没有其他更改)

注意:即使我做了
if(user.save(flush:true))
而不仅仅是
if(user.save())
,但没有将post显式保存为
post.save(flush:true,failOnError:true)
,它也不起作用。我必须明确地保存这篇文章

既然保存
用户
应该自动保存
帖子
,这种行为仍然让我感到困惑。如果有人能解释这种行为,那将非常有帮助。感谢那些花时间调查此事的人



更新-- 彼得·莱德布鲁克的解释如下():

您使用的是哪个版本的Grails?我刚刚试过Grails2.2.1 在测试中得到了一个NullPointerException。升级到2.2.4固定版本 这个特别的问题。可能是圣杯的问题

仅供参考,您甚至不需要在中显式保存用户实例 保留该帖子的操作。所以肯定是出了问题。这个 级联存储被破坏,可能是在Grails的模拟数据库中 在单元测试期间使用的实现。我猜 应用程序正常运行时工作正常(例如通过run app)


我也犯了同样的错误,我认为问题在于

 def post = new Post(params)
正如它所说

params.id: "chuck_norris"
到后期创作,但奇怪的是不是params.content

我通过将线路改为

def post = new Post(content: params.content)

在控制器中保存用户时,您是否也尝试过刷新<代码>用户。保存(刷新:真,失败者错误:真)?使用
failOnError
保存时,还要查看它是否失败。谢谢,@dmahapatro。事实上,是的,我也试过。也就是说,如果我将
if(user.save())
替换为
if(user.save(flush:true,failOnError:true))
中的
addPost()
,它不会失败。相反,前两个测试仍然通过,只有在第三行
Post.countByUser(chuck)==1时失败(与我原来的问题完全相同)。但是,如果在保存后我
println post.id
,它会给出
null
(这是一个提示,但我仍然不明白它如何能够保存用户而不会失败,但我无法在数据库中找到帖子)。你确定
loginId
是唯一的吗?我想是的,因为我已经指定了约束
loginId(blank:false,unique:true)
,数据库中还没有其他用户。但是,我可能遗漏了什么(我是Grails的新手),因此,如果您能详细说明您的确切意思,我可以试一试。谢谢!可能是因为您在测试中的
params
中有
id
。只需将文章内容直接保存为
new Post(内容:params.content)
。如果可能,将
params.id
更改为
params.loginId
,则前者具有误导性,不公平。
params.id: "chuck_norris"
def post = new Post(content: params.content)