开发者

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"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜