Coding

Implementing the examples

This page describes coding fixtures in Java. Click the toggle buttons above to choose other options.

Overview

Concordion fixtures find commands in the instrumented specification and use them to verify the system under test.

Concordion is a test runner that can invoke your application code directly:

Fixture calling application code directly

It can also drive the interfaces of your deployed application:

Fixture calling driver calling deployed application

It is good practice to create a separate driver layer for the code that drives your application interface, keeping the runner free of driver logic and the driver layer free of test assertions. Other libraries are required for the driver layer, such as Selenium WebDriver for web applications; wslite, JAX/WS or JAX/RS for web services; JDBC for databases, etc. Any Java library can be used with Concordion, providing a lot of flexibility in how you use it. See the FAQ for further detail.

Project Structure

Dependencies

Concordion requires a number of libraries to be present, including Concordion, JUnit, XOM and OGNL libraries.

The best way to manage these dependencies is to use a build tool such as Gradle or Maven. See the download page for details of the dependencies section that must be added to the build.gradle or pom.xml file respectively.

As an alternative, you can download the full distribution from the download page. After extracting the files, you must add references to all the downloaded libraries to the project classpath.

Locating the Specification

The fixture must be in the same package as the specification. It can be in a different source folder, as long as that folder is on the Java classpath. For instance the tutorial uses the conventional src/test/java folder for the fixture class, and the src/test/resources folder for the specification:

Folder structure of tutorial project

Some users prefer to have the specifications and fixture classes in the same folder, and adjust their build tool settings accordingly. Another common pattern is to create a distinct folder, such as src/spec, to keep the specifications and fixtures separate from unit tests.

Fixture classes

Concordion fixtures use the JUnit library, with a specialised ConcordionRunner:

import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;

@RunWith(ConcordionRunner.class)
public class SplittingNamesFixture {
}

A fixture class is required for each specification.

Unlike JUnit, we don’t annotate methods with @Test. Instead, the tests are determined from the specification. Each example in the specification that uses the example command is created as a separate JUnit test, and an additional [outer] test is added for any commands that exist outside of examples. If the example command is not used, the specification is run as a single JUnit test.

Fixture methods

Parameter types

Methods in Concordion fixtures can take the following parameter types:

  • Numeric types (int, long, float, double, decimal)
  • string
  • bool

When executed from a Concordion specification, Concordion will automatically convert the value to the parameter type. Boolean values must be specified as true or false in the specification.

Return types

Methods can return void, a primitive or an Object.

Returning a Map result

As described in the tutorial, to check more than one result of a behaviour, you can return an Object from the execute command. An alternative is to return a Map object, for example:

public Map split(String fullName) {
    String[] words = fullName.split(" ");
    Map<String, String> results = new HashMap<String, String>();
    results.put("firstName", words[0]);
    results.put("lastName", words[1]);
    return results;
}

This is particularly useful when calling existing methods that return Map objects, or when using a JVM language with native language support for Maps, such as Groovy

Returning a MultiValueResult

The MultiValueResult class makes it even simpler to return more than one result from the execute command. For example, SplittingNamesTest can be simplified to:

package example;

import org.concordion.api.MultiValueResult;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;

@RunWith(ConcordionRunner.class)
public class SplittingNamesFixture {

    public MultiValueResult split(String fullName) {
        String[] words = fullName.split(" ");
        return new MultiValueResult()
                .with("firstName", words[0])
                .with("lastName", words[1]);
    }
}

The specification can reference the properties of the MultiValueResult as if they were bean properties, as shown in the Splitting Names specification.

Implementation status

For specifications that don’t use the example command. you can include partially-implemented specifications in your normal build without breaking the build, by annotating your fixture classes with one of the following annotations:

  • @ExpectedToPass
  • @ExpectedToFail
  • @Unimplemented

For example:

import org.concordion.api.ExpectedToFail;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;

@ExpectedToFail
@RunWith(ConcordionRunner.class)
public class GreetingTest {

   public String greetingFor(String firstName) {
        return "TODO";
   }
}

Further details: annotation specification.

If you are using the example command, you must set the implementation status on the individual examples in the specification.

Fail-fast

After an exception occurs, by default, Concordion continues processing the current specification so it can show all the problems not just the first one.

In cases where you want Concordion to stop processing the current specification after any exception occurs, add the @FailFast annotation to the fixture class.

The @FailFast annotation has an optional onExceptionType parameter that allows a list of specific exception types to be specified. In this case, Concordion will only stop processing if any of the specified exception types occur. For example:

import org.concordion.api.FailFast;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;

@FailFast(onExceptionType={DatabaseUnavailableException.class, IOException.class})
public class MyDataTest {

   public void connectToDatabase() {
        ....
   }
}

Further details: Fail-Fast specification and Fail-Fast on specific exceptions specification.

If using the run command, adding the @FailFast annotation to the corresponding fixture will cause the specification to fail-fast if any of the specifications it runs fail-fast. For this to work, the @FailFast annotation is required on the fixture classes for both the calling and called specifications.

Field Scope

Concordion encourages you to keep your examples completely independent of each other. This allows individual examples to be run in isolation. It also makes the specification easier to follow when you can read examples in isolation.

To support this behaviour, Concordion reinitialises the fields in fixture objects for each Concordion example (where the example is using the example command). This is standard JUnit behaviour (in fact, JUnit creates a new fixture object for each test).

However, we recognise that sometimes you will want to share fields across a specification when the field is expensive to initialise, for example a browser instance or database connection. Concordion provides support for specification scoped instance fields, in addition to the default example scope.

For example:

@ConcordionScoped(Scope.SPECIFICATION)
private ScopedObjectHolder<AtomicInteger> specScopedCounter = new ScopedObjectHolder<AtomicInteger>() {
    @Override
    protected AtomicInteger create() {
        return new AtomicInteger();
    }
};

Note: fields for extensions that are annotated with @Extension default to specification rather than example scope.

See the ScopedField specification for details.

The concordion-scope-examples project demonstrates the possible combinations of scope (suite, specification, example) and runner (serial, parallel) using a web test suite where the browser is created per example, per specification or once for the whole suite.

Before and After hooks

The before and after hooks mark fixture methods to be invoked before or after an example, specification or suite:

  Before After
Example @BeforeExample @AfterExample
Specification @BeforeSpecification @AfterSpecification
Suite @BeforeSuite @AfterSuite

Note:

  1. the example hooks require you to use the example command.
  2. the suite hooks must be in the fixture that is being run directly by JUnit. Suite hooks on “child specifications” that are being run using the run command will be ignored.

    For example, assuming we have the following suite:

         Product
             Theme 1
                  Feature 1
                      Sub-Feature 1
                      Sub-Feature 2
                  Feature 2
    

    where the specification at each level contains a run command that runs its child specifications. When the Product fixture is run, its suite hooks will be invoked. The suite hooks on the child specifications that are invoked by the run command will be ignored.

    A common pattern is to have a base fixture class that contains common suite hooks and is extended by all fixtures. This allows a fixture at any level of the suite (eg. Product, Theme, Feature, Sub-Feature) to be run with the common suite hooks invoked at the start and end of the suite.

See the Concordion scope examples project for example usage of before and after hooks.

Configuration Options

The @ConcordionOptions annotation allows you to:

  • configure extensions to the Markdown syntax,
  • add namespace declarations to Markdown specifications, and
  • output the source HTML that are generated for alternate specification types, which is useful for debug purposes.

Adding resources

The @ConcordionResources annotation can be used to add new CSS, JavaScript, images, or other resources to your specification in order to tweak or completely overhaul the existing styling.

This annotation:

  • can be applied to all classes in the fixture’s class hierarchy - specify the resources once and have them applied everywhere, or limit the changes to a single fixture
  • includes wildcard support
  • can be configured to either embed or link JavaScript and CSS files in the output specification
  • can be configured to exclude Concordion’s default styling from the output specification

As an example, executing the following fixture:

package resources.test;

import org.concordion.api.ConcordionResources;
import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;

@RunWith(ConcordionRunner.class)
@ConcordionResources( value = { "resources.*", "/resource?.c??" } )
public class ConcordionResourcesDemoTest {
}

adds the following resources to the generated specification:

  • resources.js
  • resources.txt
  • ../../resources.css

See the ConcordionResources specification for other examples.

Full OGNL

Concordion deliberately restricts the expression language for instrumenting specifications.

This can be overridden by annotating the fixture with @FullOGNL to allow complex expresssions.

Adding Extensions

Extensions are added to Concordion fixtures by:

  • Adding the @org.concordion.api.extension.Extensions annotation to the fixture class. This annotation is parameterised with a list of extension, and/or extension factory, classes to be installed.

For example:

@RunWith(ConcordionRunner.class)
@Extensions({LoggingTooltipExtension.class, TimestampFormatterExtension.class})
public class MyTest {
...
  • Or adding the @org.concordion.api.extension.Extension annotation to fields in the fixture class. This allows the extensions to be configured per class instance. For example:
...
@Extension
ConcordionExtension extension = new ScreenshotExtension().setScreenshotTaker(camera);
...
  • Or by setting the system property concordion.extensions to a comma separated list of extension, and/or extension factory, classes. For example:
java -Dconcordion.extensions="org.concordion.ext.LoggingTooltipExtension,com.acme.Extra"

For further details see the extension configuration specification.

Creating an extension

The Extensions API allows you to add functionality to Concordion, for example implementing new commands, listening to events, or modifying the Concordion output.

For full details, see the extension specifications and the extensions API. Refer also to the source code for the published [extensions]((/extensions/java/markdown/).

Extensions must implement the ConcordionExtension interface, which allows extensions to hook into the Concordion build phase through the ConcordionExtender interface.

Example: Adding a style to “set” commands

As a simple example, the input style extension adds styling to set commands. To do this, it listens to set commands and adds some CSS:

public class InputStyleExtension implements ConcordionExtension {

  private static final String INPUTSTYLE_CSS_SOURCE_PATH = "/org/concordion/ext/inputstyle/inputstyle.css";
  private static final Resource INPUTSTYLE_CSS_TARGET_RESOURCE = new Resource("/inputstyle.css");

  @Override
  public void addTo(ConcordionExtender extender) {
    InputStyleOutputter outputter = new InputStyleOutputter();
    extender
      .withSetListener(outputter)
      .withLinkedCSS(INPUTSTYLE_CSS_SOURCE_PATH, INPUTSTYLE_CSS_TARGET_RESOURCE);
  }
}

where InputStyleOutputter is:

public class InputStyleOutputter implements SetListener {

    @Override
    public void setCompleted(SetEvent event) {
        event.getElement()
            .addStyleClass("inputvalue");
    }
}

If you’d prefer to embed the CSS in the HTML, rather than link it, use concordionExtender.withEmbeddedCSS(). Similar methods exist for including JavaScript in the output, or you can use withResource() to copy images or other resources to the output. (Note: These methods are useful when writing extensions that also perform additional functionality. If all you want to do is add CSS, JS or resources, see the adding resources section above.)

For other examples, see the source code of the extensions listed on the extensions page.