Music, software, life… and stuff.
[ Twitter ] [ GitHub ] [ Linked In ]
The following is an introduction to writing extensions with everybody’s favourite testing tool Spock. Like all things in Spock, extensions are easy to use and require little ceremony. We are going to build an extension that times various parts of test execution and blindly writes the times to standard out.
Please note that the API we are going to go through should not be considered stable/final.
We are building against Spock 0.5-SNAPSHOT and the following APIs may change on the road to 1.0.
Our extension is going to be annotation driven. We are going to provide an annotation that users can place on various parts of their specs. At the core of this is the IAnnotationDrivenExtension contract…
package org.spockframework.runtime.extension;
import java.lang.annotation.Annotation;
import org.spockframework.runtime.model.*;
public interface IAnnotationDrivenExtension<T extends Annotation> {
void visitSpecAnnotation(T annotation, SpecInfo spec);
void visitFeatureAnnotation(T annotation, FeatureInfo feature);
void visitFixtureAnnotation(T annotation, MethodInfo fixtureMethod);
void visitFieldAnnotation(T annotation, FieldInfo field);
void visitSpec(SpecInfo spec);
}
You’ll notice that (almost) all of Spock’s internals are implemented in Java. In this exercise we will implement our extension bits in Groovy.
Annotation driven extensions implement the Visitor Pattern, and Spock will invoke your visitation method for each annotation of your type present on the spec (We’ll discuss the visitSpec() method later).
visitSpecAnnotation() - is called when the spec class is annotated.visitFeatureAnnotation() - is called for each “feature” (i.e test method).visitFixtureAnnotation() - is called for each of the setupSpec(), setup(), cleanup() and cleanupSpec() methods that are annotated.visitFieldAnnotation() - is called for each spec instance variable.visitSpec() - is always called after all of the visitation methods. This allows you to collect a model while visiting, then decorate the spec in some manner.These methods all take the annotation instance and an info object which describes the annotated thing. For more detail on each of these info types above what we will go through in this exercise, check them out in the Spock source. They are pretty straightforward.
We need to wire our annotation up to our extension. We declare our annotation like normal, but annotate it with the IAnnotationDrivenExtension implementation that backs it.
import java.lang.annotation.*
import org.spockframework.runtime.extension.ExtensionAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.TYPE, ElementType.METHOD])
@ExtensionAnnotation(TimingExtension)
public @interface Time {}
Here we have declared a new annotation called @Time that takes no parameters and can be placed on types (in our context, the spec class) and methods. The @ExtensionAnnotation meta annotation is how tell Spock who deals with the @Time annotation.
Now we need to actual implement our extension. As you would expect, Spock provides a AbstractAnnotationDrivenExtension super class that we can use. This allows us to only implement the methods we need to. In our case, we don’t need the visitFieldAnnotation() method.
import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
import org.spockframework.runtime.model.SpecInfo
import org.spockframework.runtime.model.FeatureInfo
import org.spockframework.runtime.model.MethodInfo
class TimingExtension extends AbstractAnnotationDrivenExtension<Time> {
def timeSpec = false
def timedFeatures = []
void visitSpecAnnotation(Time annotation, SpecInfo spec) {
timeSpec = true
}
void visitFeatureAnnotation(Time annotation, FeatureInfo feature) {
timedFeatures << feature.name
}
void visitSpec(SpecInfo spec) {
spec.addListener(new TimingRunListener(timeSpec, timedFeatures)
}
}
Here we simply collect information on which things we are timing, then add a run listener (explained below) that will do the timing.
The implementation of the visitation methods in AbstractAnnotationDrivenExtension always throw an org.spockframework.runtime.InvalidSpecException. Our annotation can legally be placed on any fixture methods according to it’s definition, but that doesn’t make sense in the context of our extension. Because we have not overridden the visitFixtureAnnotation() method in our extension, if the user annotates a fixture method with @Time, they will get a nice error message from Spock that tells them that that is invalid.
It’s important to consider that there is guaranteed to only ever be one instance of an extension for each spec. Spock will create an instance of your extension the first time it encounters your annotation, and then reuse that instance when it encounters later annotations (in the same spec).
Spock has it’s own execution listening API specified by IRunListener.
package org.spockframework.runtime;
import org.spockframework.runtime.extension.builtin.StepwiseExtension;
import org.spockframework.runtime.model.*;
public interface IRunListener {
void beforeSpec(SpecInfo spec);
void beforeFeature(FeatureInfo feature);
void beforeIteration(IterationInfo iteration);
void afterIteration(IterationInfo iteration);
void afterFeature(FeatureInfo feature);
void afterSpec(SpecInfo spec);
void error(ErrorInfo error);
void specSkipped(SpecInfo spec);
void featureSkipped(FeatureInfo feature);
}
This is all pretty straightforward, but you may be wondering about beforeIteration() and afterIteration(). This are for parameterised (or data-driven) feature methods and are called for each set of parameters.
Again, we have convenient super class to work with.
import org.spockframework.runtime.AbstractRunListener;
import org.spockframework.runtime.model.*;
class TimingRunListener extends AbstractRunListener {
def timeSpec = false
def specStartTime
def featureStartTimes = [:]
TimingRunListener(timeSpec, timedFeatures, timedFixtures) {
this.timeSpec = timeSpec
timedFeatures.each { featureTimes[it] = null }
}
private now() {
System.currentTimeMillis()
}
private start(name) {
println "starting $name …"
}
private done(name, started, finished = now()) {
println "$name took ${finished - started} milliseconds"
}
void beforeSpec(SpecInfo spec) {
if (timeSpec) {
specStartTime = now()
start("spec '$spec.name'")
}
}
void beforeFeature(FeatureInfo feature) {
if (featureStartTimes.containsKey(feature.name)) {
featureStartTimes[feature.name] = now()
start("feature '$feature.name'")
}
}
void afterFeature(FeatureInfo feature) {
def startedAt = featureStartTimes[feature.name]
if (startedAt) {
echo("feature '$feature.name'", startedAt)
}
}
void afterSpec(SpecInfo spec) {
if (specStartTime) {
echo("spec '$spec.name'", specStartTime)
}
}
}
Our extension is now complete… let’s try it out.
Now all we have to do is annotate our spec…
import spock.lang.*
@Time
class DoingStuff extends Specification {
@Time
def "a b c"() {
when:
Thread.sleep(1000)
then:
true
}
}
If we were to run this spec we would see something like…
starting spec 'DoingStuff' …
starting feature 'a b c' …
feature 'a b c' took 1000 milliseconds
spec 'DoingStuff' took 1500 milliseconds
Obviously the times are made up but you get the point.
Spock ships with a bunch of extensions (as of today in 0.5-SNAPSHOT):
@AutoCleanup - allows a named cleaning up/closing method to be automatically called by Spock on a field @FailsWith - makes a feature pass only if it raises a certain exception and it’s uncaught@Ignore - prevents the feature or spec from running@IgnoreRest - prevents every other feature from running@RevertMetaClass - undoes any changes to the specified to meta classes of the specified classes during the feature (or spec)@Stepwise - causes each feature method to be executed in declaration order and any after the first failure to be ignored (i.e. story mode)@Timeout - causes a method to fail if it takes to long (and will interrupt it if it does take too long)For more info on any of these extensions, check out their javadoc comments.
You could very easily make your own extensions specific to your projects to make your life easier. You could load data fixtures, establish connections… all kinds of things.
So that’s “Annotation Driven Extensions” with the Spock Framework.
Go forth and create some… and may the force be with you (wait… what?)
Posted: Aug 18th, 2010 @ 10:58 pm
Tags: #groovy spock
The following is a proposal for, and a request for comment on, the idea of a semi-structured combination of social and technological systems and norms to initiate, develop and maintain Grails plugins.
Typically, a plugin is born through inspiration or necessity. It’s not uncommon that the original author at some point stops maintaining or developing a frequently-used or promising plugin. There are many reasons for this and they are in no way unique to the Grails ecosystem. People contribute and move on as is their want and well intentioned developers can quickly lose interest in developing a plugin due to roadblocks and lack of knowledge, experience and feedback.As a Software Developer using Grails professionally, the availability of a high quality plugin makes a significant product difference; and conversely an unsuccessful attempt to use a plugin due to bugs or lack of documentation costs real time. Often when finding an issue and fixing it, getting that fix back into the community takes considerable effort in coordinating with the plugin developer/maintainer who may or may not be around.
As a plugin developer, during busy times it can be hard to keep up with bug reports, feature requests and Grails upgrades. It’s also concerning that knowledge and understanding of the plugins I have authored rest largely with myself, creating a single point of failure. It would be much better if there were other people who could (and did) maintain and improve things in my (or any other author’s) absence.
To remedy this situation, I propose that a self-organised, distributed, democratic, collective be formed… to foster, develop and maintain Grails plugins. I am calling this idea the “Grails Plugin Collective” for the time being.
The social structure is what would be the most interesting and, ultimately, enabling. It would allow members to work across plugins, scratching their own itches, and getting those fixes/features into release with minimal effort without risking quality. It would also enable steering and guiding the direction of plugins while allowing members to freely propose ideas and alternatives. Due to Grails’ “convention over configuration” nature, this becomes critically important. Hopefully, the collective wisdom of a group of individuals results in a highly consistent, conventional approach to how plugins work.
I propose that the collective should be an entity outside of VMWare. This does not exclude VMWare employees from participating in the collective by any means, or from having some kind of special role. It purely means that the entity is not bound to VMWare in any way other than through the licensing arrangement of any software owned by VMWare.
I also propose that if a plugin certification scheme is ever established, it be done completely separately to the collective. I imagine that many of the collectivised plugins would aim for certification if at all possible, but in this respect they would be no different to other community plugins.
The collective should aim to operate with the least amount of bureaucracy and procedure that is required. The core idea is that as a democratic structure, all proposals are put to a vote. Members will have a relatively short time frame (proposed: 48 hours) to vote. A certain proportion of the votes cast must be affirmative for the proposal to succeed. It’s likely that there would also be some sort of veto mechanism for things that are extremely time critical.
I image the following kinds of things would be voted on:
Being that what is being proposed is a self-organising, democratic social structure, I am hesitant to be to prescriptive about how such a thing might work in terms of my vision. I am interested in getting other people’s ideas.
An initial membership also needs to be put together. This will be somewhat of a challenge for a democratic outfit. To get the ball rolling, I suggest that the VMWare Grails team propose an initial membership of roughly 10 people (likely including themselves). From there, the collective can self organise and induct new members over time.
So what are your thoughts? Is such an initiative worthwhile? Could it work? Could it succeed in raising and maintaining the quality of selected Grails plugins? Are there other such initiatives that we could learn from? What kind of social practices could be put in place to support rapid development while maintaining consistency and quality?
Feel free to leave your opinion either here or on the grails-user mailing list.
Posted: Jun 7th, 2010 @ 9:29 pm
I haven’t seen many cases of people putting the Grails’ artefact API to use in their application. It’s likely that most Grails’ users aren’t aware that it exists, which in many ways is a good thing… it is after all an implementation concern.
You can however put it to work in your own application.
Perhaps it’s best to discuss what an artefact is in Grails first. An example of an artefact is a service class. Think of what happens when your grails application starts; it finds all the service classes, creates an instance of each (typically) and autowires the dependencies. Grails defines a class whose instances represent a single service class and an associated handler. This is what I am collectively calling the Artefact API.
The biggest reason these days is by far the hot reloading support. That is, if you change an artefact class while the application is running, Grails (with some help from you) will reload the class and you get the changes without a restart. Of course, we are all familiar with this.
There is a more subtle benefit though. You may have some kind of prominent & pervasive concept in your application (like a service is a concept). You might find that creating an artefact for this concept can make your life easier.
Consider some kind of eStore, with a wide range of products. Our products have simple persistence requirements but can have wildly varying behaviours in the system, which are likely to get more complex over time. I try to avoid extensive subclassing of domain objects when the classes primarily vary in behaviour and not data. So we need to find another way to implement the varying behaviour.
ProductHandlerEach product is going to provide a handler (terrible name I know) that is responsible for the majority of the product’s behaviour. Here is our product domain class…
class Product {
String name
// don't want to try and persist the handler
static transients = ['handler']
ProductHandler getHandler() {
// somehow get the handler for this product
}
}
And we have a ProductHandler interface for good measure…
interface ProductHandler {
void setProduct(Product)
boolean isCanBeSold()
}}
One kind of traditional approach would be to have each product persist a key that refers to a particular product handler, and to have each product handler class register itself with some kind of registry.
class Product {
String name
String handlerKey
def productHandlerService // autowired
// don't want to try and persist the handler
static transients = ['handler']
protected handler
ProductHandler getHandler() {
if (!handler) {
handler = productHandlerService[this]
}
handler
}
}
// Our registry
import grails.spring.BeanBuilder
class ProductHandlerService {
def grailsApplication // autowired
static private registry = [:].asSynchronized()
static register(String key, Class handlerClass) {
registry[key] = handlerClass // should check that handlerClass implements ProductHandler
}
protected createBeanBuilder() {
new BeanBuilder(grailsApplication.mainContext, grailsApplication.classLoader)
}
protected instantiateHandler(handlerClass, Product product) {
createBeanBuilder().with {
beans {
handler(handlerClass, product: product) {
// autowire the handler so it can use services etc.
it.autowire = true
}
}
createApplicationContext().getBean('handler')
}
}
def getAt(Product product) {
def handlerClass = registry[product.handlerKey] // assume there is always a handler
instantiateHandler(handlerClass, product)
}
}
// Example Handler (in src/groovy dir)
class OnlyOnTuesdayProductHandler implements ProductHandler {
static {
ProductHandlerService.register('onlyOnTuesday', OnlyOnTuesdayProductHandler)
}
Product product
boolean isCanBeSold() {
new GregorianCalendar().get(Calendar.DAY_OF_WEEK) == Calendar.TUESDAY
}
}
This will get the job done (albeit in a cumbersome way), but it suffers from ProductHandler implementations not being hot-reloadable.
ProductHandler artefactWe can make things a bit easier and support hot reloading by creating an artefact for product handlers.
To do this we need a plugin…
class ProductHandlerGrailsPlugin {
def version = "0.1"
def grailsVersion = "1.2.* > *"
// register the artefact handler
def artefacts = [ProductHandlerArtefactHandler]
// watch for any changes in these directories
def watchedResources = [
"file:./grails-app/productHandlers/**/*ProductHandler.groovy",
"file:../../plugins/*/productHandlers/**/*ProductHandler.groovy"
]
// Grails calls this when one of the files matching 'watchedResources' changes.
// We have to actually swap in the new class when the source changes
// There isn't anything special here, it's just boilerplate.
def onChange = { event ->
if (application.isArtefactOfType(ProductHandlerArtefactHandler.TYPE, event.source)) {
def oldClass = application.getProductHandlerClass(event.source.name)
application.addArtefact(ProductHandlerArtefactHandler.TYPE, event.source)
// Reload subclasses
application.productHandlerClasses.each {
if (it.clazz != event.source && oldClass.clazz.isAssignableFrom(it.clazz)) {
def newClass = application.classLoader.reloadClass(it.clazz.name)
application.addArtefact(ProductHandlerArtefactHandler.TYPE, newClass)
}
}
}
}
}
We also need a artefact handler implementation (which has to be in Java due to legacy reasons)…
import org.codehaus.groovy.grails.commons.ArtefactHandlerAdapter;
public class ProductHandlerArtefactHandler extends ArtefactHandlerAdapter {
// the name for these artefacts in the application
static public final String TYPE = "ProductHandler";
// the suffix of all product handler classes (i.e. how they are identified as product handlers)
static public final String SUFFIX = "ProductHandler";
public ProductHandlerArtefactHandler() {
super(TYPE, ProductHandlerClass.class, DefaultProductHandlerClass.class, SUFFIX);
}
}
And a ProductHandlerClass interface (again in Java)…
import org.codehaus.groovy.grails.commons.GrailsClass;
public interface ProductHandlerClass extends GrailsClass {}
And a DefaultProductHandlerClass implementation of that interface (again in Java)…
import org.codehaus.groovy.grails.commons.AbstractGrailsClass;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;
public class DefaultProductHandlerClass extends AbstractGrailsClass implements ProductHandlerClass {
public DefaultProductHandlerClass(Class clazz) {
super(clazz, ProductHandlerArtefactHandler.SUFFIX);
}
// This method will get the static property 'key' on the underlying
// ProductHandler class that it represents.
public String getKey() {
Object key = GrailsClassUtils.getStaticPropertyValue(getClazz(), "key");
if (key == null) {
return null;
} else {
return key.toString();
}
}
}
That’s a shotgun intro to the Grails Artefact API classes like GrailsClass and ArtefactHandlerAdapter just covering the stuff we need. Check out the linked to source for deeper detail.
Here is what we have done…
grails-app/productHandlers in the app and pluginsonChange handler that reloads product handler classes when they changeArtefactHandler implementation that specifies what a product handler class isGrailsClass subinterface for product handler classeskey on the underlying class (among other things)Armed with our new artefact, we can now simplify our initial implementation…
// Our registry
import grails.spring.BeanBuilder
class ProductHandlerService {
def grailsApplication // autowired
protected createBeanBuilder() {
new BeanBuilder(grailsApplication.mainContext, grailsApplication.classLoader)
}
protected instantiateHandler(handlerClass, Product product) {
createBeanBuilder().with {
beans {
handler(handlerClass, product: product) {
// autowire the handler so it can use services etc.
it.autowire = true
}
}
createApplicationContext().getBean('handler')
}
}
def getAt(Product product) {
def productHandlerClasses = grailsApplication.productHandlerClasses // a list of DefaultProductHandlerClass
def handlerClass = productHandlerClasses.find { it.key == product.handlerKey } // find the one with the matching key
instantiateHandler(handlerClass, product)
}
}
// Example Handler (in grails-app/productHandlers dir)
class OnlyOnTuesdayProductHandler implements ProductHandler {
static key = 'onlyOnTuesday'
Product product
boolean isCanBeSold() {
new GregorianCalendar().get(Calendar.DAY_OF_WEEK) == Calendar.TUESDAY
}
}
Each of our product handler classes must specify a key, that products use to link themselves to a particular handler implementation. You could do away with this, and infer the key from the class name quite easily.
class ProductHandlerService {
// …snip…
def getAt(Product product) {
def handlerClass = grailsApplication.getProductHandlerClass(product.handlerKey)
instantiateHandler(handlerClass, product)
}
}
The grailsApplication.getProductHandlerClass() method given the value "onlyOnTuesday" would return the OnlyOnTuesdayProductHandler class object.
In this particular case I personally wouldn’t do this as every time I renamed a product handler class I would have to update the products that use that handler in the DB, but other situations may not have this issue.
All product handler classes are now reloadable, which would obviously be a substantial productivity boost, without all that much work on our part. This approach could be used for any kind of concept or set of classes in your application that don’t fit into the standard controller, service, taglib etc. pattern.
The Artefact API can seem a bit confusing if you don’t take into consideration that this was the heart of Grails in the early days. It’s still there and is used by a great many plugins (e.g. grails-quartz) as well as being used extensively internally in Grails, but it doesn’t quite have the same role that it used to.
Hopefully this has shown you how you can, and why you might want to, use the artefact API internally in your applications.
Posted: Jun 1st, 2010 @ 10:42 pm
I recently hit the case where I need to forward a request in Grails to a second action, but that action needed access to the model generated by the first (note that this is different to params). The Grails documentation doesn’t mention the ability to pass models when forwarding at all, but if you check the source code you can see that the capability is there.
What’s also missing is how to access the existing model in a forwarded to or chained to action. If you trace the code, you see that the model eventually gets passed to Spring’s [WebUtils.exposeRequestAttributes()](http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/web/util/WebUtils.html#exposeRequestAttributes(javax.servlet.ServletRequest, java.util.Map)) method, that simply makes each item in the model a request attribute. So in Grails you can access them via the request variable…
class MyController {
def first = {
forward(action: 'second', model: [thing: "thing"])
}
def second = {
assert request.thing == "thing"
// go forth and do stuff
}
}
Params can only be strings where as you can pass any type via the model.
Posted: May 24th, 2010 @ 2:35 pm
Recently there was a query on the grails-user mailing list asking how to create a separate test suite (in the poster’s terminology). In Grails’ terminology, what the poster wanted was a separate test type that could be run separately.
The following is an expansion on the solution offered in the thread.
There are two core concepts in the test system; types and phases.
A test phase refers to the state of the application at test time. Grails comes with 4…
Unit - no application state, only the classpath is configuredIntegration - application running (e.g. database, beans), but not listening for http requestsFunctional - application is fully runningOther - same as UnitHere’s how to create your own test phase.
In your scripts/_Events.groovy file…
// 1. add the name of your phase to this variable with this event handler
eventAllTestsStart = {
phasesToRun << "custom"
}
// 2. Create a «phase name»Tests variable containing a list of test types (more on this later)
customTests = ["custom"]
// 3. Create pre and post closures
customTestPhasePreparation = {
// called at the start of the phase
}
customTestPhaseCleanUp = {
// called at the end of the phase
}
Even if you don’t have anything to do in your Preparation and CleanUp closures, you still need to create empty ones.
Note that the name of a test phase has no real correlation to it’s type’s names. In this example we just happen to have a type with name custom in a phase named custom.
If you need your phase to be like one of the standard phases, just call that phase’s handlers…
customTestPhasePreparation = {
integrationTestPhasePreparation()
}
customTestPhaseCleanUp = {
integrationTestPhaseCleanUp()
}
That’s all there is to it.
A test type is really two things; a grouping of tests and a test running mechanism.
Above we had…
… // 2. Create a «phase name»Tests variable containing a list of test types (more on this later) customTests = ["custom"] …
The «phaseName»Tests lists contains a phase’s test types. These must either be String’s or instance of GrailsTestType. Writing a GrailsTestType implementation will be the subject of a future article (if you are interested, start with the JavaDoc on GrailsTestType and then look at implementations in the wild like the one from the Spock plugin).
Grails ships with the JUnit4GrailsTestType which is what Strings get implicitly converted to (this happens here) for both convenience and legacy reasons.
Test types are associated to a certain directory (under test) and one or more class name suffixes. The JUnit test type uses the suffix Tests pre Grails 1.3 and Tests and Test in Grails 1.3. The directory for String test types is the value (i.e. test/custom for our above example).
You don’t need to write pre/post handlers for types like you do with phases. But should you need to, here is how you do it…
eventTestSuiteStart = { typeName ->
if (typeName == 'custom') {
// do stuff
}
}
eventTestSuiteEnd = { typeName ->
if (typeName == 'custom') {
// do stuff
}
}
The event has that name due to legacy reasons.
Above we created an integration-like phase called custom. Without a bit of extra work, our tests won’t be completely like standard integration tests in that they won’t have transaction rollback and other bits and pieces. To do this you need to register a JUnit4GrailsTestType so it can be configured. Here is a complete example on how you would do this…
import org.codehaus.groovy.grails.test.junit4.JUnit4GrailsTestType
// 1. add the name of your phase to this variable with this event handler
eventAllTestsStart = {
phasesToRun << "custom"
}
// 2. Create a custom test type
def testTypeName = "custom"
def testDirectory = "custom"
def testMode = new GrailsTestMode(autowire: true, wrapInTransaction: true, wrapInRequestEnvironment: true)
def customTestType = new JUnit4GrailsTestType(testTypeName, testDirectory, testMode)
// 3. Create a «phase name»Tests variable containing the test type(s)
customTests = [customTestType]
// 4. Create pre and post closures
customTestPhasePreparation = {
integrationTestPhasePreparation()
}
customTestPhaseCleanUp = {
integrationTestPhaseCleanUp()
}
For a functional-like phase you would do…
import org.codehaus.groovy.grails.test.junit4.JUnit4GrailsTestType
// 1. add the name of your phase to this variable with this event handler
eventAllTestsStart = {
phasesToRun << "custom"
}
// 2. Create a custom test type
def testTypeName = "custom"
def testDirectory = "custom"
def testMode = new GrailsTestMode(autowire: true)
def customTestType = new JUnit4GrailsTestType(testTypeName, testDirectory, testMode)
// 3. Create a «phase name»Tests variable containing the test type(s)
customTests = [customTestType]
// 4. Create pre and post closures
customTestPhasePreparation = {
functionalTestPhasePreparation()
}
customTestPhaseCleanUp = {
functionalTestPhaseCleanUp()
}
(functional tests don’t need rollback transactions or fake request environments)
For completeness, here’s a unit-like phase (though you could just use a String)…
import org.codehaus.groovy.grails.test.junit4.JUnit4GrailsTestType
// 1. add the name of your phase to this variable with this event handler
eventAllTestsStart = {
phasesToRun << "custom"
}
// 2. Create a custom test type
def testTypeName = "custom"
def testDirectory = "custom"
def customTestType = new JUnit4GrailsTestType(testTypeName, testDirectory)
// 3. Create a «phase name»Tests variable containing the test type(s)
customTests = [customTestType]
// 4. Create pre and post closures
customTestPhasePreparation = {
unitTestPhasePreparation()
}
customTestPhaseCleanUp = {
unitTestPhaseCleanUp()
}
Once registered and configured, your custom tests can be run just like any other tests using grails test-app. You can even target them like normal…
grails test-app custom: // run all types in the custom phase
grails test-app :custom // run the custom type in all phases
grails test-app custom:custom // run the custom type in the custom phase
You can even target tests and test methods
grails test-app custom:custom Person* Product.testSave
The above example would run all test classes starting with Person (and ending in Test or Tests) and the testSave test in ProductTest or ProductTests.
The Grails manual has a section on running tests.
The original email query was asking for a solution for having a test type that didn’t run with grails test-app, but ran when explicitly requested. Given the above, the following should make sense…
eventAllTestsStart = {
if (testOptions.customOnly) {
phasesToRun = ["integration"] // remove the other phases
integrationTests = [] // remove the normal integration tests
addCustomTestType()
} else if (testOptions.withCustom) {
addCustomTestType()
}
}
addCustomTestType = {
integrationTests << "ipbx"
}
(The testOptions map contains all switch or parameter style arguments passed to grails test-app)
So this gives us…
grails test-app // don't run the custom type
grails test-app -only-custom // run ONLY the custom type
grails test-app -with-custom // run ALL types including our custom type
Laziness on my part. This article will likely be the basis of what will end up in the manual (GRAILS-6301).
Posted: May 20th, 2010 @ 9:21 pm
Tags: #software #grails #testing