My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
PluginDevelopment  
How to create your own plugins
Updated Feb 4, 2010 by mathieu....@gmail.com

Creating your own plugins for Mycila Testing Framework is very easy. As an example is better than many words, let's start by a tutorial on how to write a JMock Plugin.

The Plugin API

TestPlugin

Plugins must implement TestPlugin, but we hardly advise you extend DefaultTestPlugin to avoid APi breaks and to avoid implementing all methods unecessary for you.

Your plugin will be called at each step of a test flow:

  • prepareTestInstance
  • beforeTest
  • afterTest
  • afterClass

In these plugin methods, you have access to the current Execution context and the current Test context. The test context represents the test class and the Execution context represents the method under test / execution. The table below summary the contexts you can obtain for each method. Note that you can retrieve the Test context from the Execution context by doing execution.context()

Plugin method Available contexts
prepareTestInstance TestContext context is passed as a parameter

Execution execution = Mycila.currentExecution();
beforeTest TestExecution context is passed as a parameter
TestExecution execution = (TestExecution) Mycila.currentExecution();
TestContext ctx = execution.context();
afterTest TestExecution context is passed as a parameter
TestExecution execution = (TestExecution) Mycila.currentExecution();
TestContext ctx = execution.context();
afterClass TestContext context is passed as a parameter

Execution execution = Mycila.currentExecution();

Context

The Execution and sub class TestExecution context represents the method in test. When you develop a plugin that handles beforeTest and afterTest events, the TestExecution context enables you to set wheter the test method should be skipped, and enables you to get the exception thrown by the test method if any.

With those 2 properties, you are able to change the behavior of the test framework and let some test pass even if they thrown an exception.

A good example of this is the AnnotationPlugin which provides @Skip and @ExpectException annotations

Introspector and Filters

The TestContext class gives you access to an Introspector on the test instance, from where you can select fields and methods through filters and compose them. In example, in Guice plugin, to select all methods providing Guice Modules and annotated by @ModuleProvider, we can call:

import static com.mycila.testing.core.introspect.Filters.*;

List<Method> methods = ctx.introspector().selectMethods(excludeOverridenMethods(and(methodsReturning(Module.class), methodsAnnotatedBy(ModuleProvider.class))))

The Filters class already provides common filters. The Introspector is in a separate package and can be used on any class instance, and you can also define your own filters. In exemple, here we want to select from myObject all methods annotated by @ConfigureMycilaPlugins which have only one parameter, a PluginManager instance.

Introspector introspector = new Introspector(myObject);
List<Method> methods = introspector.selectMethods(excludeOverridenMethods(and(methodsAnnotatedBy(ConfigureMycilaPlugins.class), new Filter<Method>() {
    @Override
    protected boolean accept(Method method) {
        final Class<?>[] types = method.getParameterTypes();
        return types.length == 1 && types[0].equals(PluginManager.class);
    }
})));

Configure PluginManager / Test your plugin

You can use @MycilaPlugins and @ConfigureMycilaPlugins to test your plugin, as described in the User API wiki page.

Basically, @MycilaPlugins enables you to control which plugin descriptor to use (to isolate your plugin when testing) and this annotation enables you to configure per-test the PluginManager.

If you want to define your own plugin at runtime like this example, you'll have to annotate your configuration method with @ConfigureMycilaPlugins.

Example

An example of implementation would be:

public final class MyPlugin extends DefaultTestPlugin{
    @Override
    public List<String> getBefore() {
        return null;
    }
    @Override
    public List<String> getAfter() {
        return null;
    }
    @Override
    public void prepareTestInstance(Context context) {
    }
}

We don't care about receiving beforeTest, afterTest and afterClass events.

getBefore and getAfter comes from Mycila Plugin Framework. If provided, they help defined the order of execution of the plugins.

In example, suppose we have a Spring plugin that checks for beans to include in the Application Context. If we execute the JMock Plugin before the Spring plugin, we will be able to create the Spring Application Context base on created mocks.

The TestContext enables the plugin to access the test instance to enhance, and also sharing data with other plugins through context attributes. A plugin can also access the plugin manager and resolver, which provides the plugin execution flow. To have more information about the plugin infrastructure, please see Mycila Plugin Framework documentation.

First, What do we want for our plugin ?

  • Create and inject mock
  • Be able to access the mockery to verify expectations

We would like to be able to write tests using JMock like this:

public final class MyTest extends MycilaTestNGTest {

    @MockContext
    Mockery mockery;

    @Mock
    Service service;

    @Test
    public void test_go() {
        mockery.checking(new Expectations() {{
            // some expectations
        }});
        // execute test
        mockery.assertIsSatisfied();
    }
}
  • @Mock defines a mock to create and inject
  • @MockContext defines a place where to inject the mockery

Let's code our plugin !

First, we need to have two annotations:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Mock {
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface MockContext {
}

Then, we prepare our plugin class:

public final class MyPlugin extends DefaultTestPlugin {
    @Override
    public List<String> getAfter() {
        return Arrays.asList("guice1", "spring");
    }
    @Override
    public void prepareTestInstance(Context context) {
        //TODO: implement
    }
}

The implementation is quite simple:

  1. Get the mocks to create (by listing fields annotated by @Mock)
  2. Create a mockery
  3. If @MockContext annotated fields are found, inject the Mockery
  4. Create and inject mocks
Field[] mocks = context.getTest().getFieldsAnnotatedWith(Mock.class);
Mockery mockery = new Mockery();
for (Field field : context.introspector().selectFields(and(fieldsAccepting(Mockery.class), fieldsAnnotatedBy(MockContext.class)))) {
    context.introspector().set(field, mockery);
}
for (Field field : mocks) {
    context.introspector().set(field, mockery.mock(field.getType(), field.getDeclaringClass().getName() + "." + field.getName()));
}

Package and distribute

Now that we have a working and testable plugin, it would be nice if it could be loaded automatically if our plugin jar is in the classpath. To detect plugins, Mycila Plugin Framework reads all property files in the classpath named:

META-INF/mycila/testing/plugins.properties

So we just have to include a plugins.properties files in our jar, with the following content:

jmock=MyPlugin

Et voila ! We've made with 3 classes (two annotation and a plugin class) and a property file, a JMock plugin for Mycila Testing, that can be automatically loaded and executed to enhance your tests.

To go further

We highly suggest you have a look of existing plugins implementation in the source code repository. There are plenty of plugins existing and you will probably find what you need on these examples.


Sign in to add a comment
Powered by Google Project Hosting