Odd behavior with default index action when unit t

2019-09-05 15:06发布

Using grails 2.4.4 (and 2.4.5) I created the following tiny application.

grails create-app example
cd example
grails create-domain-class Book

Then edited Book.groovy to look like

package example
class Book {
    String title;
    static constraints = {
    }
}  

Then added a basic Controller

grails> create-controller example.book
| Created file grails-app/controllers/example/BookController.groovy
| Created file grails-app/views/book
| Created file test/unit/example/BookControllerSpec.groovy

and modify the controller to extend RestfulController.

package example
import grails.rest.*
class BookController extends RestfulController<Book> {
    static responseFormats=['json','xml']
    BookController() {
        super(Book)
    }
}

Hook up the UrlMappings to serve up the books as a resource at /api/books.

class UrlMappings {
  static mappings = {
    "/api/books"(resources: "book")
    "/"(view:"/index")
    "500"(view:'/error')
  }
}

and last but not least, built a few books in BootStrap.groovy.

import example.*
class BootStrap {
    def init = { servletContext ->
        new Book(title: "Book 1").save(failOnError:true);
        new Book(title: "Book 2").save(failOnError:true);
        new Book(title: "Book 3").save(failOnError:true);
    }
    def destroy = {
    }
}

then grails run-app

And everything looks good. The example.Book controller shows up in the index page. The three books can be viewed as json or xml.

So now to the unit test.

Edit the BookControllerSpec to look like this

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

@TestFor(BookController)
@Mock(Book)
class BookControllerSpec extends Specification {

    def setup() {
        new Book(title: "Unit Test Book 1").save(failOnError:true);
        new Book(title: "Unit Test Book 2").save(failOnError:true);
        new Book(title: "Unit Test Book 3").save(failOnError:true);
    }
    void "My Setup Worked"() {
        given: "My setup ran"
        when: "I ask for the count"
        then: "I should get back 3"
        Book.count() == 3;
    }

    void "I can access my Controller index method"() {
        given: "My setup ran"
        when:  "I call index"
        request.method="GET"
        response.format="xml"
        controller.index();

        then: "I get an XML object back with 3 books"
        println response.contentAsString
        response.xml.book*.title.size()==3
    }
}

The first test passes, and the 2nd one fails. The output from the println in the failing test is an empty xml list of books.

<?xml version="1.0" encoding="UTF-8"?><list />

While investigating what was going on I looked at the index method of the parent class (RestfulController).

By copying that method into BookController, the unit test will start passing.

-- new version of BookController with copied index method --

package example
import grails.rest.*
class BookController extends RestfulController<Book> {
    static responseFormats=['json','xml']
    BookController() {
        super(Book)
    }

    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond listAllResources(params), model: [("${resourceName}Count".toString()): countResources()]
    }
}

So is there anything else I need to add to the Spec in order to get the index method of the RestfulController to work without having to copy the index method into the BookController?

Any idea why the call to listAllResources works when executed directly from BookController, but returns no rows when executed from the RestfulController.

The unit test above is a modified version of the rest unit tests described in the Grails In Action book, which is written for grails 2.3. http://www.manning.com/gsmith2/

1条回答
姐就是有狂的资本
2楼-- · 2019-09-05 15:10

Unit tests can get really tricky with grails. I never determined exactly what causes this failure, but I did discover another oddity.

Inserting the failing test into the spec file twice will cause it to fail the first time, but pass the 2nd time.

void "first call to index doesn't work."() {
    given: "My setup ran"
    when:  "I call index"
    request.method="GET"
    response.format="xml"
    controller.index();

    then: "I get an XML object back with 3 books"
    println response.contentAsString
    response.xml.book*.title.size()==3
}

void "2nd call to index works"() {
    given: "My setup ran"
    when:  "I call index"
    request.method="GET"
    response.format="xml"
    controller.index();

    then: "I get an XML object back with 3 books"
    println response.contentAsString
    response.xml.book*.title.size()==3
}

Something deep in the bowels of the grails testing framework doesn't successfully hook up that index method the first time it is called.

Instead of digging any further, I just rewrote this as an integration test and it started behaving properly.

There is a lot of magic you get for free with grails unit testing, but when the magic doesn't work it's probably better to try a different testing phase.

查看更多
登录 后发表回答