1 Introduction - Reference Documentation
Authors: Erik Pragt (jWorks.nl), Marcin Erdmann, John Engelman
Version: 2.0.3
Table of Contents
1 Introduction
The Fitnesse plugin provides a integration point between the popular Open Source testing framework Fitnesse:http://www.fitnesse.org and Grails applications.This guide documents the usage of the plugin and provides an easy starting point to get you up to speed with the plugin as well as with Fitnesse.Note that the SLIM version of Fitnesse is supported. FIT, the older Fitnesse protocol, is not yet supported, nor will it be supported in the near future.
Release History
- April 26, 2012
- 2.0.3 Release!
- Small bugfix in the _Events.groovy, due to conflicts in closure name with Functional test plugin.
- Another small bugfix in the _Install.groovy. The .donotdelete file for the fitnesse directory was created in the wrong dir.
- April 23, 2012
- 2.0.2 Release!
- Small bugfix in the _Install.groovy. Thanks to Nicolas Vinet for reporting!
- April 16, 2012
- 2.0.1 Release!
- Implemented better feedback
- Fixed some bugs in the documentation, most notably the quickstart.
- Enabled PDF support for documentation
- Enabled bundled example project
- April 12, 2012
- 2.0 Release!
- After a long time of inactivity, there's a new release, thanks to John Engelman. Thanks for the great work John!
- JUnit Test Reports implemented
- Included latest Fitnesse (20111025)
- June 10, 2011
- 1.0 Release!
- Feature: Support for GivWenZen
- Bugfix: Fixed the tutorial documentation (thanks Pierre D. Tremblay!)
- Bugfix: Fixed the dependency resolution of libraries (thanks Steef de Bruijn)
- Bugfix: Fixed plugin scopes (thanks Steef de Bruijn)
- May 18, 2011
- 0.95 Release
- Feature: Enabled easy mapping of enums classes
- Improvement: Improved Query Fixture DSL to allow dotted notation
- Improvement: Enhanced reloading of application
- March 22, 2011
- 0.9 Release
- We upped the version number to 0.9 because the plugin is quite feature complete now, and is almost stable enough to be a 1.0.
- Feature: Introduced @Fixture annotation, so fixtures can now be given any name (they don't have to be suffixed with Fixture), as long as they are annotated with the annotation.
- Feature: Added Transaction support
- Bugfix: Fixed JSON conversion for domain classes and collections
- Bugfix: Fixed reloading classes and fixtures that use inheritance
- Bugfix: Fixed grails test-app
- Bugfix: Fixed issues in the documentation
- March 1, 2011
- 0.5 Release
- Feature: Fitnesse testrunner created. Grails applications can now be tested using: grails test-app integration:fitnesse "FrontPage.GrailsTestSuite.SlimTestSystem?suite"
- Feature: Fitnesse integrated. Fitnesse can now be started using: grails run-fitnesse
- Feature: Fitnesse can be disabled by setting the grails.plugin.fitnesse.disabled property to true
- Improvement: Internal Fitnesse upgraded to the newest Fitnesse version
- Improvement: Naming of configuration parameters have changed to be more consistent. *Beware: the 'plugins' configuration option has changes to 'plugin' (without the 's')
- Improvement: Bundled testproject in Github
- Improvement: Fixture classes are now Spring beans
- Bugfix: Fixed lazyloadingexception
- Bugfix: Fixed exception message when a given constructor is not found for a fixture
- Bugfix: Fixed reloading of services to give errors in Fixtures
- Bugfix: Marcin's surname is fixed in the documentation!
- November 16, 2010
- 0.4 Release, thanks to Marcin Erdmann!
- Complete refactoring of the plugin thanks to Marcin Erdmann (ie using Artefacts, Artefact Templates, JSON Code to enable complex objects, and more!)
- Important: All fixtures should now be end with the suffix 'Fixture', as in 'CalculateFixture'. In the Wiki you can still refer to them as 'Calculate', but the plugin adds a Fixture suffix when looking for the class.
- Improved error messages
- You can now create complex objects from within Fitnesse by using JSON syntax.
- More documentation!
- Fixed some bugs in documentation (thanks Olivier Hedin for reporting!)
- October 12, 2010
- 0.3 Release.
- Added more documentation (configuration options, quickstart), refactored the internals
- Added verbose logging switch
- Fixed a Grails reloading bug, which caused ports to be opened twice. Now the plugin closes all ports, and reopens them whenever Grails forces a restart
- You can now throw StopTest Exceptions from Fixture constructors (which is not possible in the Java Fitnesse)
- September 19, 2010
- 0.2 release. First public release. Includes lots of documentation, including a tutorial with 3 fixture types.
- September 15, 2010
- initial 0.1 release.
1.1 Features
The Fitnesse plugin supports some additional features besides the standard Fitnesse functionality.Moved Fitnesse to it's own Phase/Type (since 2.0)
You can now usegrails test-app fitnesse:fitnesse
. The phase runs in integration modeIntegration with JUnit reports (since 2.0)
The Fitnesse output, which is written to target/test-reports/fitnesse, will now be parsed to create JUnit Test objects. This allows the JUnit Reporting framework to output the .txt file and the JUnit .xml file. This also allows Fitnesse test results to participate in the Grails Test Report HTML file.Support for GivWenZen (since 1.0)
Since version 1.0, we support the GivWenZen library. GivWenZen allows a user to use the BDD Given When Then vocabulary and plain text sentences to help a team get the words right and create a ubiquitous language to describe and test a business domain. See the GivWenZen website for more information.To use GivWenZen in Grails Fitnesse, import thefitnesse.grails
package and use the GivWenZenForGrails
fixture in a script table, as can be seen in the example below:|import| |fitnesse.grails||script| |start|giv wen zen for grails||script| |given|the number 5| |when|incrementing it by 3| |then|the result is 8||script| |given|the number 1| |when|incrementing it by 2| |and|incrementing it by 3| |then|the result is 6|
import org.givwenzen.annotations.DomainStep import org.givwenzen.annotations.DomainSteps@DomainSteps class GivWenZenSupportSteps { private int number @DomainStep("the number (\\d+)") void setNumber(int number) { this.number = number } @DomainStep("incrementing it by (\\d+)") void incrementNumber(int by) { number += by } @DomainStep("the result is (\\d+)") boolean expect(int result) { number == result } }
Automatic Enum mapping (since 0.95)
Since version 0.95, it's possible to automatically map enum values. Just specify the value of the enum in the table, and you're done.An example:class MyEnumFixture { Color color }enum Color { RED, GREEN, BLUE }
|my enum | |color|color?| |RED |RED |
Nested property mapping for Query Fixtures (since 0.95)
It was already possible (since version 0.4) to create a simple Query Fixture using the Query Fixture DSL. This DSL has been extended by also allowing nested properties using a dotted (.) notation, as can be seen below:class NestedPropertyKeyValueQueryFixture { static queryFixture = true static mapping = ["name":"author.name", "birthYear":"author.birthYear", "title":"title"] def queryResults() { return [new Book(title:'Grails in Action', author: new Author(name:"Peter Ledbrook", birthYear: 1980))] } }
Fixture annotation (since 0.9)
Since version 0.9, it's possible to annotate Fixture classes with the @Fixture annotatation. This means that fixtures can now be given any name (ie. they don't have to be suffixed with Fixture), as long as they are annotated with the @Fixture annotation.@Fixture class BuyBook { // contents here }
Transaction support (since 0.9)
Transaction support provides three fixtures called BeginTransaction, Commit and Rollback. To use you need to import the fitnesse.grails package, i.e. in SetUp or SuiteSetUp:|import|
|fitnesse.grails|
begin transaction
and rollback
tables to your test page:|begin transaction|--some test tables--|rollback|
Templates (since 0.4)
You can now easily create Fixtures by typing:grails create-fitnesse-query-fixture <name of fixture>
grails create-fitnesse-fixture <name of fixture>
Complex objects (since 0.4)
You can now create complex objects from within Fitnesse. This uses the JSON format, since it's easy to read and write, and is becoming quite a standard.An example can be seen below:Wiki|create book inventory | |book |amount| |{author: Stephen King, title: IT} | 3| |{author: Dean Koontz, title: Chase}| 5|
class CreateBookInventoryFixture { Book book int amount def bookService CreateBookInventoryFixture() { Book.list()*.delete() } void execute() { amount.times { book.id = null bookService.addBook(book) } } }
class Book { String author String title }
|json objects conversion with collections|
|producer|models|match?|
|{name: 'Audi', models: [{name: 'A3'}, {name: 'A4'}]}|[{name: 'A3'}, {name: 'A4'}]|true|
class JsonObjectsConversionWithCollectionsFixture {
CarProducer producer List<CarModel> models boolean match() {
producer.models*.name.containsAll(models*.name)
}
}
class CarProducer { String name static hasMany = [models: CarModel] }class CarModel { String name }
Query Fixture DSL (since 0.4)
To make writing Query fixtures much easier, we've introduced the concept of a simple mapping DSL. This means that mapping values from, let's say a services, becomes almost trivial.Consider the following Service method:def checkInventory() {
Book.executeQuery("select b.title, b.author, count(*) from Book b group by title, author")
}
class CheckBookInventoryFixture { static queryFixture = true // indication that this is a query fixture static mapping = [title: 0, author: 1, amount: 2] // the mapping def bookService // injected service def queryResults() { // queryResults() method, which must be named like this! bookService.checkInventory() } }
class BookService { List<Book> checkInventory() { return Book.list() } }class CheckBookInventoryFixture { static queryFixture = true // indication that this is a query fixture static mapping = ["title", "author", "amount"] // a different mapping def bookService // injected service def queryResults() { // queryResults() method, which must be named like this! bookService.checkInventory() } }
class CheckBookInventoryFixture { static queryFixture = true // indication that this is a query fixture static mapping = ["title":"objectTitle", "author":"theAuthor", "amount":"amount"] // a different mapping, where the key is the name of the test, and the value the property name of the object def bookService // injected service def queryResults() { // queryResults() method, which must be named like this! bookService.checkInventory() } }
Strings as methods
Groovy supports methods like"this is a method"
. The Fitnesse plugin also supports this, making some Fixtures more readable.Example
class MyFixture { boolean "check if customers exists"(int customerNumber) { // … } }
Default arguments
Groovy supports default arguments. The Grails Fitnesse plugin also supports this:class MyFixture { MyFixture(boolean clearDatabase = false) { // … } }
|my fixture|true|
Untyped arguments
Groovy supports untyped method arguments. The Grails Fitnesse plugin also supports this:class MyFixture {
boolean checkCustomer(customerNumber) {
// …
}
}
Note that the Fitnesse SLIM protocol only supports Strings and Lists. A result of this, is that when using untyped arguments, things could go wrong. An example of this is when using an integer. This integer is interpreted as being a String, and hence it's converted to it's value, so int 1 becomes int 49. So, if you're unsure, use types in your fixtures.
Automatic reloading of Fixtures
The Fixtures in thegrails-app/fitnesse
directory are automatically reloaded and injected by Grails. This enables faster testing and faster development!Functions are getters
Fitnesse decision tables do not have getters and setters, but setters and functions. A function is the same as a getter, but without the 'get' part. (Still with me?). An example:Normal Fitnesse|my decision fixture| |digit | roman?| |1 | I| |5 | V|
class MyDecisionFixture { int digit String roman void execute() { roman = RomanNumberConverter.convertDigit(digit) } String roman() { return roman } }
class MyDecisionFixture { int digit String roman void execute() { roman = RomanNumberConverter.convertDigit(digit) } }