Skip to content

Implementing Specifications

Harry Cummings edited this page May 10, 2017 · 15 revisions

Concept

The fundamental building block of a JarSpec Specification is a statement about the behaviour of your application, together with an automated test for that statement. These building blocks may be grouped into larger units of behaviour. The structure is flexible, but may look something like this:

  • Major unit of behaviour
    • Statement of behaviour
    • Statement of behaviour
    • Minor unit of behaviour
      • Statement of behaviour
    • Minor unit of behaviour
      • Statement of behaviour
      • Statement of behaviour

A 'unit of behaviour' may correspond to a class, or package, or method, but it doesn't have to directly correspond to any of these components of the static model visible in your code. You may, for example, wish to consider a certain set of interactions between objects at runtime to constitute a unit of behaviour. You are free to structure your Specifications in whichever way allows you to communicate most clearly.

Implementation

Specifications must implement the io.hgc.jarspec.Specification interface and be annotated to run with the io.hgc.jarspec.JarSpecJUnitRunner:

package io.hgc.jarspec.examples;

import io.hgc.jarspec.JarSpecJUnitRunner;
import io.hgc.jarspec.Specification;
import org.junit.runner.RunWith;

@RunWith(JarSpecJUnitRunner.class)
public class DocumentationSpec implements Specification {
    ...
}

The Specification interface requires you to implement a root() method that returns the parent node of the whole specification (i.e. the top-level bullet point in the outline above). This should be a description of the unit of behaviour covered by your Specification:

    ...
    @Override
    public SpecificationNode root() {
        return describe("Documentation example", ...);
    }
    ...

This node may contain one or more statements:

    ...
        return describe("Documentation example",
            it("demonstrates Statement nodes", () -> assertTrue(true)),
            it("demonstrates multiple Statement nodes", () -> assertTrue(true))
        );
    ...

Units may contain a mix of statements and other nested units:

    ...
        return describe("Documentation example",
            it("demonstrates Statement nodes", () -> assertTrue(true)),
            it("demonstrates multiple Statement nodes", () -> assertTrue(true)),
            describe("nested unit",
                it("demonstrates single-statement case", () -> assertTrue(true))
            )
        );
    ...

For details of the describe and it methods, see the API Docs for the Specification interface

Rules

JUnit test rules allow you to add extra behaviour around tests, e.g. for managing shared state. For more details, see Setup and Teardown

Mixins

Mixins may provide additional convenience methods for writing statements that represent common patterns of behaviour. To write your own mixin, you just need to create a new interface with a default method. The following mixins come with the framework:

  • ExceptionBehaviour, for making statements about exception behaviour. See the ExceptionSpec fixture for a usage example.
  • AsynchronousBehaviour, for making statements about asynchronous behaviour. See the async fixtures for usage examples.

Selective execution

(Also see Sequence of Execution)

Skipping specifications or parts of a specification

You can tell JUnit to skip an entire spec by adding a standard JUnit @Ignore annotation to the class. You can also skip individual tests or whole units of behaviour by calling .skip() on the value returned by it() or describe(). Finally, it is possible to describe a behaviour without providing a test for it, in which case it will also appear as skipped (amber state) in the JUnit test results.

See the SkippedTestSpec fixture for examples of all of these.

Running a single spec or parts of a spec exclusively

You can run a single specification in the usual ways that JUnit allows you to run a single test class (by providing its fully-qualified name as a command-line argument, or by selecting it your IDE's JUnit runner).

You can also exercise individual tests or units within specification by calling .only() on the value returned by it() or describe(). If you call only() on multiple parts of a specification then they will all run. Other tests in the same specification will appear as ignored in the JUnit test results. Other specifications will not be affected.

See the SelectiveTestExecutionSpec fixture and the the SelectiveDescribeExecutionSpec fixture for examples.