- Grails Doc
- Grails in Action - book
- The Definitive Guide to Grails - book
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
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
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() } }
Thank you very much for blogging about this. Very helpful!
ReplyDeleteRegards,
Julius
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.
ReplyDeleteA very helpful post. Thanks!
ReplyDeleteff
ReplyDeleteThank you Tomas for this great post.
ReplyDeleteIt is much more clear and useful than the default grails documentation.
Nice post.
ReplyDeleteIs 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?
Great article helps clear all ambiguities. By the way i was trying to unit test restplugin using mock objects. Any idea how write test.
ReplyDeleteGetting following exception on the Service where i am making rest call, withRest
groovy.lang.MissingMethodException: No signature of method:
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