How to implement a Cucumber-JVM plugin

Filed under: Cucumber, — Tags: Cucumber-JVM, Plugin — Thomas Sundberg — 2019-03-07

Cucumber-JVM comes with some built in plugins. They are mostly used for reporting. If the built in plugins aren't enough for your needs, it is possible to implement your own plugin.

This blog post will show you how to

Implement a plugin that can receive events from Cucumber

The first step is to create a class that implements one of the interfaces

Both define the method void setEventPublisher(EventPublisher publisher);. The difference is that events sent to cucumber.api.event.ConcurrentEventListener are sent from a publisher that is synchronized. The documentation says that

"Events are not published concurrently".

If you don't have any specific reason for not being thread safe, implement cucumber.api.event.ConcurrentEventListener.

Register the events the plugin should listen for

What you need to do is to register callbacks so the proper events can be received. Each receiver must implement the interface cucumber.api.event.EventHandler<T extends Event>

A solution that is common in the plugins built in into Cucumber-JVM is to create an implementation of the interface and delegate to an instance method.

The built in plugin cucumber.runtime.formatter.JSONFormatter declares an instance variable like this:

private EventHandler<TestSourceRead> testSourceReadHandler = new EventHandler<TestSourceRead>() {
    @Override
    public void receive(TestSourceRead event) {
        handleTestSourceRead(event);
    }
};

This instance variable delegates to a method called handleTestSourceRead(event); implemented in the same class.

The handlers are then registered in the method public void setEventPublisher(EventPublisher publisher). The cucumber.runtime.formatter.JSONFormatter looks like this:

@Override
public void setEventPublisher(EventPublisher publisher) {
    publisher.registerHandlerFor(TestSourceRead.class, testSourceReadHandler);
    publisher.registerHandlerFor(TestCaseStarted.class, caseStartedHandler);
    publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler);
    publisher.registerHandlerFor(TestStepFinished.class, stepFinishedHandler);
    publisher.registerHandlerFor(WriteEvent.class, writeEventhandler);
    publisher.registerHandlerFor(EmbedEvent.class, embedEventhandler);
    publisher.registerHandlerFor(TestRunFinished.class, runFinishedHandler);
}

There is a total of seven different events that cucumber.runtime.formatter.JSONFormatter listens for.

There is a total of eleven different events that can be registered. They are:

Configure the plugin

Plugins are configured through their constructor.

Create either a default constructor or a constructor that takes an argument. The constructor that takes an argument is used to configure the plugin. The possible types are

The proper constructor will be located by cucumber.runtime.formatter.PluginFactory and their constructor that accepts a String will be used.

The constructor for JSONFormatter looks like this:

public JSONFormatter(Appendable out) {
    this.out = new NiceAppendable(out);
}

A complete example

An example may look like this:

package se.thinkcode.report;

import cucumber.api.event.ConcurrentEventListener;
import cucumber.api.event.EventHandler;
import cucumber.api.event.EventPublisher;
import cucumber.api.event.TestRunStarted;
import cucumber.runtime.CucumberException;

import java.io.File;

public class ReportThingy implements ConcurrentEventListener {
    private File reportDir;

    public ReportThingy(String outputDir) {
        createOutputDir(outputDir);
    }

    private void createOutputDir(String outputDir) {
        reportDir = new File(outputDir);
        if (!reportDir.exists() && !reportDir.mkdirs()) {
            throw new CucumberException("Failed to create dir: " + outputDir);
        }
    }

    private EventHandler<TestRunStarted> runStartedHandler = new EventHandler<TestRunStarted>() {
        @Override
        public void receive(TestRunStarted event) {
            startReport(event);
        }
    };

    @Override
    public void setEventPublisher(EventPublisher publisher) {
        publisher.registerHandlerFor(TestRunStarted.class, runStartedHandler);
    }

    private void startReport(TestRunStarted event) {
        System.out.println(event.getTimeStamp());
    }
}

Use your plugin

How do you use the plugin then? When you use one of the built in plugins, you can declare it like this:

@CucumberOptions(
        plugin = "pretty"
)

A custom plugin is declared with its complete name like this:

@CucumberOptions(
        plugin = "se.thinkcode.report.ReportThingy:./build/cucumber/report"
)

If you use the command line then you would declare it as

--plugin se.thinkcode.report.ReportThingy:./build/cucumber/report

The part to the right of the colon is the configuration, in this case ./build/cucumber/report which is a directory that the plugin should use.

Conclusion

Implementing a Cucumber plugin is a four-step process

Acknowledgements

I would like to thank Malin Ekholm and Mika Kytöläinen for feedback.

Resources



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
DevOps
Executable specification
Git
Gradle
Guice
J2EE
JUnit
Java
Javascript
Kubernetes
Linux
Load testing
Maven
Mockito
New developers
Pair programming
PicoContainer
Presentation
Programming
Public speaking
Quality
React
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS