Grails 1.3.5 controller test nulls command object
Straight from page 104 of "The Definitive Guide to Grails (Second Edition)":
void testLoginUserNotFound() {
mockRequest.method = "POST"
mockDomain(User)
MockUtils.prepareForConstraintsTests(LoginCommand)
def cmd = new LoginCommand(login:"fred", password:"letmein")
cmd.validate()
controller.login(cmd)
assertTrue cmd.hasErrors()
assertEquals开发者_JAVA技巧 "user.not.found", cmd.errors.login
assertEquals "/store/index", renderArgs.view
}
When this test is run, it fails with:
junit.framework.AssertionFailedError: junit.framework.AssertionFailedError: null
...which I tracked down to the "cmd" reference being null at that point. Before the action controller.login is called, cmd is valid and filled, afterwards, it is null.
How can I test command objects?
cmd
is not null
; however, assertTrue
throws an AssertionFailedError
with a null
message. Either provide a default message (assertTrue "default message", cmd.hasErrors()
) or just state, assert cmd.hasErrors()
.
So, let's see why cmd.hasErrors()
returns false
. - That's because the custom validator does not return false
, but some string instead, that evaluates to true
according to the "Groovy Truth". (There, the Grails API appears to have changed since the book has been published.)
In the LoginCommand
class, change
login blank:false, validator:{ val, cmd ->
if(!cmd.user)
return "user.not.found"
}
to
login blank:false, validator:{ val, cmd ->
if(!cmd.user)
return false
}
Then, the error code will be "LoginCommand.login.validator".
If you need a custom error code, you can add an error object yourself (without returning anything), like so:
login blank:false, validator:{ val, cmd ->
if(!cmd.user)
cmd.errors.rejectValue('login', 'user.not.found')
}
P.S.: You shouldn't use the MockUtils
class, directly, but instead extend GrailsUnitTest
. Also, MockUtils.prepareForConstraintsTests(Class)
is deprecated; moreover, it doesn't suit well for command objects. - Use the mockForConstraintsTests(Class)
method instead, inherited from GrailsUnitTest
.
I use this method in controller unit tests to add extra plumbing for command objects:
private def invoke(String action) {
def types = controller."$action".parameterTypes
if (types && types.length == 1) {
Class cmdClass = types[0]
mockCommandObject(cmdClass)
def cmd = cmdClass.newInstance()
controller.params.each{ key, value ->
try{ cmd."$key" = value } catch(MissingPropertyException ex){}
}
cmd.validate()
controller."$action"(cmd)
} else {
controller."$action"()
}
}
The test looks like this:
setup:
controller.params.contractNum = "invalid"
when:
invoke "lookup"
精彩评论