Friday, August 6, 2010

Grails: Unit testing

Source:


The core of the testing plugin is the grails.test.GrailsUnitTestCase class.


mockDomain(class, testInstances)

Takes a class and makes mock implementations of all the domain class methods.

example:
void testSomething() {
  def testInstances=[]
  mockDomain(Song, testInstances)
  assertEquals(0, Song.count())
  new Song(name:"Supper's Ready").save()
  assertEquals(1, Song.count())
}

example:
def testInstances = [ new Item(code: "NH-4273997", name: "Laptop"),
                new Item(code: "EC-4395734", name: "Lamp"),
                new Item(code: "TF-4927324", name: "Laptop") ]
mockDomain(Item, testInstances)



mockForConstraintsTests(class, testInstances)

Highly specialised mocking for domain classes and command objects that allows you to check whether the constraints are behaving as you expect them to.

example:
class Book {
  String title
  String author
    static constraints = {
        title(blank: false, unique: true)
        author(blank: false, minSize: 5)
    }
}

class BookTests extends GrailsUnitTestCase {
  void testConstraints() {
      def existingBook = new Book(title: "Misery", author: "Stephen King")
      mockForConstraintsTests(Book, [ existingBook ])
        // Validation should fail if both properties are null.
        def book = new Book()
        assertFalse book.validate()
        assertEquals "nullable", book.errors["title"]
        assertEquals "nullable", book.errors["author"]
        // So let's demonstrate the unique and minSize constraints.
        book = new Book(title: "Misery", author: "JK")
        assertFalse book.validate()
        assertEquals "unique",     book.errors["title"]
        assertEquals "minSize", book.errors["author"]
        // Validation should pass!
        book = new Book(title: "The Shining", author: "Stephen King")
        assertTrue book.validate()
    }
}



mockFor(class)

 

This range determines how many times you expect the method to be called, so if the number of invocations falls outside of that range then an assertion error will be thrown. If no range is specified, a default of "1..1" is assumed.

The closure arguments should match the number and types of the mocked method, but otherwise you are free to add whatever you want in the body.


example:
def strictControl = mockFor(MyService)
strictControl.demand.someMethod(0..2) { String arg1, int arg2 -> … }
strictControl.demand.static.aStaticMethod {-> … }

example:
class MyService { def otherService
    String createSomething() {        
        def stringId = otherService.newIdentifier()        
        def item = new Item(code: stringId, name: "Bangle")        
        item.save()        
        return stringId      
   }
   
   int countItems(String name) {        
        def items = Item.findAllByName(name)        
        return items.size()    
   }
}

import grails.test.GrailsUnitTestCase
class MyServiceTests extends GrailsUnitTestCase {
    void testCreateSomething() {
        // Mock the domain class.
        mockDomain(Item)

        // Mock the "other" service.
        String testId = "NH-12347686"
        def otherControl = mockFor(OtherService)
        otherControl.demand.newIdentifier(1..1) {-> return testId }

        // Initialise the service and test the target method.
        def testService = new MyService()
        testService.otherService = otherControl.createMock()

        def retval = testService.createSomething()

        // Check that the method returns the identifier returned by the
        // mock "other" service and also that a new Item instance has
        // been saved.
        def testInstances = Item.list()        
        assertEquals testId, retval
        assertEquals 1, testInstances.size()
        assertTrue testInstances[0] instanceof Item
    }

    void testCountItems() {
        // Mock the domain class, this time providing a list of test
        // Item instances that can be searched.
        def testInstances = [ new Item(code: "NH-4273997", name: "Laptop"),
                         new Item(code: "EC-4395734", name: "Lamp"),
                         new Item(code: "TF-4927324", name: "Laptop") ]
        mockDomain(Item, testInstances)
       

// Initialise the service and test the target method.

def testService = new MyService()
        assertEquals 2, testService.countItems("Laptop")
        assertEquals 1, testService.countItems("Lamp")
        assertEquals 0, testService.countItems("Chair")
    }
}


mockLogging(class, enableDebug = false)

Adds a mock "log" property to a class. Any messages passed to the mock logger are echoed to the console.




mockController(class)

Adds mock versions of the dynamic controller properties and methods to the given class. This is typically used in conjunction with the ControllerUnitTestCase class.

Mock controller properties available in unit tests
  • request
  • response
  • session
  • flash
  • params
  • redirectArgs
  • renderArgs
  • template
  • modelAndView
example:
class PostControllerUnitTests extends grails.test.ControllerUnitTestCase {
     void testShow() {
          mockDomain(User, [
                    new User(userId: "glen"),
                    new User(userId: "peter") ]
          this.controller.params.id = "peter"
          def model = this.controller.show()
          assertEquals "peter", model["viewUser"]?.userId
     }
}

example - action returns a model:
void testList() {
     mockDomain(Album, [ new Album(title: "Odelay"),
     new Album(title: "Abbey Road"] )
     def model = controller.list()
     assertEquals 2, model.albumList.size()
}

example - Testing the Contents of the Response:
void testIndex() {
     controller.index()
     assertEquals "Welcome to the gTunes store!", controller.response.contentAsString
}

example - contents of render args
render(view: "show", model:[album:Album.get(params.id)])

void testShow() {
     mockDomain(Album, new Album(id:1, title: "Aha Shake Heartbreak"))
     mockParams.id = 1
     controller.show()
     assertEquals "show", renderArgs.view
     assertEquals 1, renderArgs.model.album.id
     assertEquals "Aha Shake Heartbreak", renderArgs.model.album.title
}

mockParams: this property provides a mock implementation of the params object that you can populate with values before calling the controller. In addition to a mock implementation of the params object, the ControllerUnitTestCase class provides the following properties that mock various aspects of the controller API:
  • mockRequest
  • mockResponse
  • mockSession
  • mockParams
  • mockFlash
note:ControllerUnitTestCase not support some dynamic method. For instance: bindData(). Then is better use integration testing, or you can add this method to controller:
 example:
this.controller.metaClass.bindData = { obj, params ->
           params.each { key, value ->
                       obj."$key" = value
               }
       }

this.controller.metaClass.getGrailsApplication = {-> return [config: [:]] }


example - redirect:
//in controller
redirect(controller: "post", action: "list")

// in test
assertEquals "post", this.controller.redirectArgs["controller"]
assertEquals "list", this.controller.redirectArgs["action"]

example - testing action:
def register = {
     if(request.method == 'POST') {
          def u = new User(params)
          if(u.password != params.confirm) {
                u.errors.rejectValue("password", "user.password.dontmatch")
                return [user:u]
          }else if(u.save()) {
                session.user = u
                redirect(controller:"store")
          }else {
                return [user:u]
          }

     }
}

void testRegistrationFailed() {
     mockRequest.method = 'POST'
     mockDomain(User)

     mockParams.login = ""
     def model = controller.register()

     assertNull mockSession.user
     assert model
     def user = model.user
     assert user.hasErrors()
     assertEquals "blank", user.errors.login
     assertEquals "nullable", user.errors.password
     assertEquals "nullable",
     user.errors.firstName
     assertEquals "nullable", user.errors.firstName
}

void testRegistrationSuccess() {
     mockRequest.method = 'POST'
     mockDomain(User)

     mockParams.login = "joebloggs"
     mockParams.password = "password"
     mockParams.confirm = "password"
     mockParams.firstName = "Joe"
     mockParams.lastName = "Blogs"

     def model = controller.register()

     assertEquals 'store',redirectArgs.controller
     assertNotNull mockSession.user
}

example - test Command
def login = { LoginCommand cmd ->
     if(request.method == 'POST') {
        if(!cmd.hasErrors()) {
             session.user = cmd.getUser()
             redirect(controller:'store')
        }else {
             render(view:'/store/index', model:[loginCmd:cmd])
        }
     }else {
            render(view:'/store/index')
     }
}

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 "user.not.found", cmd.errors.login
     assertEquals "/store/index", renderArgs.view
}

void testLoginPasswordInvalid() {
     mockRequest.method = 'POST'
     mockDomain(User, [new User(login:"fred", password:"realpassword")])
     MockUtils.prepareForConstraintsTests(LoginCommand)
     def cmd = new LoginCommand(login:"fred", password:"letmein")
     cmd.validate()
     controller.login(cmd)
     assertTrue cmd.hasErrors()
     assertEquals "user.password.invalid", cmd.errors.password
     assertEquals "/store/index", renderArgs.view
}

void testLoginSuccess() {
     mockRequest.method = 'POST'
     mockDomain(User, [new User(login:"fred", password:"letmein")])
     MockUtils.prepareForConstraintsTests(LoginCommand)
     def cmd = new LoginCommand(login:"fred", password:"letmein")
     cmd.validate()
     controller.login(cmd)
     assertFalse cmd.hasErrors()
     assertNotNull mockSession.user
     assertEquals "store", redirectArgs.controller
}



mockTagLib(class)

Adds mock versions of the dynamic taglib properties and methods to the given class. This is typically used in conjunction with the TagLibUnitTestCase class.

example:
class MyTagLib {
     def repeat = { attrs, body ->
            attrs.times?.toInteger().times { n ->
                      body(n)
             }
      }
}

<g:repeat times="3">Hello number ${it}</g:repeat>

import grails.test.*
class MyTagLibTests extends TagLibUnitTestCase {

     MyTagLibTests() {
         super(MyTagLib)
     }

     void testRepeat() {
         tagLib.repeat(times: '2') {
            'output<br/>'
         }

     assertEquals 'output<br/>output<br/>', tagLib.out.toString()
     }
}

example:





One oddity is the empty map ([:]) as an argument, which is required because the tag
expects an attribute map as the first argument. If we were passing in at least one attri-
bute value, we could use the implicit syntax instead:
tagLib.isLoggedIn(attr1: "value") { ... }


In order to check whether the appropriate text has been generated by the tag, we
then perform a toString() on the out property and compare it to the expected value

mockConfig

In your controller you have to use org.codehaus.groovy.grails.commons.ConfigurationHolder. Then is very simple used mockConfig.

example:
private void takeOverDemoTestsets(school) {
       def String[] ids = ConfigurationHolder.config.cz.svse.testy.demo.testsets.ids.split(",")
       ids.each(){ id ->
           def testset = Testset.get(id)
           if(testset){
               school.addToTestsets( testset.clone(  school ) )
               log.info(" add testset: ${testset.title}" )              
           }
   }
}

class SchoolControllerTests extends grails.test.ControllerUnitTestCase {
  
    protected void setUp() {
       super.setUp()
           
       mockDomain(Testset, [
                   new Testset(id:1, title:"one"),
                   new Testset(id:2, title:"two")
       ])

       mockDomain(School,[])

       mockConfig ('''
               cz.svse.testy.demo.testsets.ids="1,2"
       ''')     
    }

    protected void tearDown() {
           super.tearDown()
    }
  
    void testTakeOverDemoTestsets() {
           
       def school = new School()
       assertEquals 0, school.testsets.size()

       this.controller.takeOverDemoTestsets(school)
       assertEquals 2, school.testsets.size()                
    }
}

9 comments:

  1. Thank you very much for blogging about this. Very helpful!

    Regards,
    Julius

    ReplyDelete
  2. Unit testing in Grails seems to confuse a lot of folks. Or at least it's changed quickly over the last releases. I found this site a great resource. Rock on.

    ReplyDelete
  3. A very helpful post. Thanks!

    ReplyDelete
  4. Thank you Tomas for this great post.
    It is much more clear and useful than the default grails documentation.

    ReplyDelete
  5. Nice post.

    Is there anything special you need to do to test filters? e.g. if you want to see how a before or after filter modifies the request object? do you need to mock the filter class like you would a controller?

    ReplyDelete
  6. Great article helps clear all ambiguities. By the way i was trying to unit test restplugin using mock objects. Any idea how write test.
    Getting following exception on the Service where i am making rest call, withRest
    groovy.lang.MissingMethodException: No signature of method:

    ReplyDelete
  7. Great article..! I am really thankful to "The publisher of this post" for writing such a great article that every grails developer needs. I appreciate you effort.

    ReplyDelete