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

Posted

技术标签:

【中文标题】在控制器测试中创建的域对象不会在数据库中结束【英文标题】:A domain object created in a controller test does not end up in the database 【发布时间】:2014-01-08 09:40:44 【问题描述】:

我一直在尝试遵循 Grails in Action(GiA MEAP,第 2 版),但时不时会发现一些问题,即使花了很多时间我似乎也无法弄清楚。这是其中之一。抱歉,这个问题与 GiA 中的示例有关(v13,ch6,清单 6.x 中的 PostControllerSpec,第 144 页),但我将尝试使用精简但完整的代码来解决问题。我有两个域模型,UserPost

package grailstuts

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

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


package grailstuts

class Post 
    String content
    static belongsTo = [ user: User]

    static constraints =  content(blank: false) 

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 == "Successfully created Post"response.redirectedUrl == "/post/timeline/$chuck.loginId",但在最后一行Post.countByUser(chuck) == 1 失败(这非常令人费解,无法弄清楚为什么会失败)。我得到以下信息:

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

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

【问题讨论】:

您在控制器中保存用户期间是否也尝试过刷新? user.save(flush: true, failOnError: true)?另请查看使用failOnError 保存时是否失败。 谢谢你,@dmahapatro。事实上,是的,我也尝试过。意思是,如果我在addPost() 中用if (user.save(flush: true, failOnError: true)) 替换if (user.save()),它确实NOT 失败。相反,前两个测试仍然通过,并且仅在第三行 Post.countByUser(chuck) == 1 失败(与我最初的问题完全相同)。但是,如果我在保存后println post.id,它会给出null(这是一个提示,但我仍然不明白它如何不失败地保存用户,但我在数据库中找不到该帖子)。 你确定loginIdunique 我想是的,因为我已经指定了约束loginId(blank: false, unique: true),并且数据库中还没有其他用户。但是,我可能遗漏了一些东西(我是 Grails 的新手),所以如果你能详细说明你的意思,我可以试一试。谢谢! 可能是因为您在测试中的params 中有id。只需将带有内容的帖子直接保存为new Post(content: params.content)。如果可能的话,将params.id 更改为params.loginId,前者具有误导性,不公平。 【参考方案1】:

经过无数次尝试,我做了一些有效的事情。但是,我仍然很困惑,如果有人能解释一下,那真的很有帮助。如果user.save()true,我对其进行的唯一更改是保存post 明确。这里是修改后的addPost()(唯一改动的那一行清楚地标明了// save the post explicitly!!,其他地方没有其他改动)。

def addPost() 
    def user = User.findByLoginId(params.id)
    if (user) 
        def post = new Post(params)
        user.addToPosts(post)
        if (user.save()) 
            post.save(flush: true, failOnError: true)       // save the post explicitly!!
            flash.message = "Successfully created Post"
         else 
            flash.message = "Invalid or empty post"
        
     else 
        flash.message = "Invalid User Id"
    
    redirect(action: 'timeline', id: params.id)

请注意:即使我做了if (user.save(flush: true)) 而不仅仅是if (user.save()),但没有将帖子的显式保存为post.save(flush: true, failOnError: true),它也不起作用。我不得不明确地保存帖子。

既然保存user 应该自动保存post,这种行为仍然让我感到困惑。如果有人可以解释这种行为,那将非常有帮助。感谢那些花时间研究这个的人。

更新 -- Peter Led*** 的解释如下 (link here):

您使用的是哪个版本的 Grails?我刚刚尝试使用 Grails 2.2.1 并在该测试中得到 NullPointerException。升级到 2.2.4 已修复 那个特别的问题。可能是 Grails 问题。

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

【讨论】:

【参考方案2】:

我有同样的错误,我认为问题出在

 def post = new Post(params)

因为它正在添加

params.id: "chuck_norris"

到 Post 创建,但奇怪的是不是 params.content。

我通过将行更改为解决了我的问题

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

【讨论】:

以上是关于在控制器测试中创建的域对象不会在数据库中结束的主要内容,如果未能解决你的问题,请参考以下文章

为什么在dialogflow中创建的丰富消息不会出现在经过Slack测试的机器人中?

Spring MVC 中创建的模型对象到底在哪里?

在前块中创建的 Rspec 变量在功能规范到达 POSTed 控制器操作时被删除

使用在另一个 Swift VC 文件中创建的对象?

在 Snappy shell 中创建的表不会显示在 JDBC 或 Pulse 中

在 RestKit 0.20.2 中创建的重复核心数据对象