Reputation: 577
I have been trying to follow the Grails in Action (GiA MEAP, 2nd edition), but every now and then I stumble on some issues that I cannot seem to figure out even after spending a lots of time. This is one of those. Sorry, this question is related to the example in the GiA (v13, ch6, PostControllerSpec in Listing 6.x, Pg. 144), but I will try to capture the issue with a stripped down but complete code. I have two domain models, User
and Post
.
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) }
}
The PostController
is as follows.
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)
}
}
When I try unit testing the controller as follows (as given in the book), I have the issue.
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
}
}
The test passes the first two tests flash.message == "Successfully created Post"
and response.redirectedUrl == "/post/timeline/${chuck.loginId}"
, but fails at the last line Post.countByUser(chuck) == 1
(which is very puzzling and cannot figure out why that fails). I get the following:
| Condition not satisfied:
Post.countByUser(chuck) == 1
| | |
0 | false
grailstuts.User : 1
My question is why does the above test fail, even though it successfully created the post? I have spent quite a bit of time trying to figure out the bug, but no luck yet.
Upvotes: 3
Views: 1105
Reputation: 61
I had the same error, I think the problem is with
def post = new Post(params)
As it was adding
params.id: "chuck_norris"
to the Post creation, but strangely not params.content.
I solved mine by changing the line to
def post = new Post(content: params.content)
Upvotes: 0
Reputation: 577
After countless tries, I did something that worked. However, I am still very puzzled, and if someone can explain it that would really help. The only change I made for it to work was saved the post
explicitly if user.save()
was true
. Here is the modified addPost()
(the only line changed is marked clearly with // save the post explicitly!!
, and there is no other change anywhere else).
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)
}
One note: Even if I did if (user.save(flush: true))
instead of just if (user.save())
but did not include the explicit save for the post as post.save(flush: true, failOnError: true)
, it did not work. I had to save the post explicitly.
Since saving the user
should have saved the post
automatically, this behavior still puzzles me. If someone can explain this behavior it would be very helpful. Thanks to those who took the time to look into this.
UPDATE --
Explanation from Peter Ledbrook below (link here):
Which version of Grails are you using? I just tried with Grails 2.2.1 and got a NullPointerException in that test. Upgrading to 2.2.4 fixed that particular problem. Could be a Grails issue.
FYI, you shouldn't even need to explicitly save the User instance in the action to persist the Post. So something is definitely wrong. The cascading save is broken, probably in Grails' mock database implementation that's used during unit tests. I'm guessing that the application works fine when run normally (via run-app for example).
Upvotes: 1