Concordion Tutorial: Acceptance Testing with Living Specifications

Concordion Tutorial: Acceptance Testing with Living Specifications

Concordion is a Java testing framework that turns plain-English specifications into executable acceptance tests. Unlike Cucumber's Gherkin syntax, Concordion embeds test instrumentation directly into HTML documents — the same documents your product team writes. The result is specifications that can be run as tests without any transformation.

What Makes Concordion Different

Most BDD frameworks require you to translate between two formats: the spec (what the business writes) and the test (what engineers execute). Concordion collapses that gap. Your HTML specification document is the test. Developers instrument the HTML with Concordion commands, link it to Java fixture code, and the framework executes the document itself.

When tests pass, Concordion renders the spec with green highlights. When they fail, you see exactly which assertions broke — directly in the document your team already reads.

Project Setup

Add the Concordion dependency to your Maven project:

<dependency>
    <groupId>org.concordion</groupId>
    <artifactId>concordion</artifactId>
    <version>2.2.0</version>
    <scope>test</scope>
</dependency>

For Gradle:

testImplementation 'org.concordion:concordion:2.2.0'

Concordion requires JUnit 4 or JUnit Vintage runner. If you're on JUnit 5, add the vintage engine:

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
</dependency>

Writing Your First Specification

Create an HTML file in src/test/resources that mirrors your fixture class package. For com.example.UserRegistration, create src/test/resources/com/example/UserRegistration.html:

<html xmlns:concordion="http://www.concordion.org/2007/concordion">
<body>

<h1>User Registration</h1>

<p>
  When a user registers with email
  <span concordion:set="#email">alice@example.com</span>
  and password
  <span concordion:set="#password">SecurePass123!</span>,
  the system should
  <span concordion:assertEquals="register(#email, #password)">succeed</span>.
</p>

<h2>Email Validation</h2>

<table concordion:execute="#result = validateEmail(#email)">
  <tr>
    <th concordion:set="#email">Email</th>
    <th concordion:assertEquals="#result">Valid?</th>
  </tr>
  <tr>
    <td>user@example.com</td>
    <td>true</td>
  </tr>
  <tr>
    <td>not-an-email</td>
    <td>false</td>
  </tr>
  <tr>
    <td>user@</td>
    <td>false</td>
  </tr>
</table>

</body>
</html>

Writing the Fixture Class

The fixture class connects your HTML specification to your application code:

package com.example;

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

@RunWith(ConcordionRunner.class)
public class UserRegistrationFixture {

    private UserRegistrationService service = new UserRegistrationService();

    public String register(String email, String password) {
        try {
            service.register(email, password);
            return "succeed";
        } catch (RegistrationException e) {
            return "fail: " + e.getMessage();
        }
    }

    public boolean validateEmail(String email) {
        return service.isValidEmail(email);
    }
}

The fixture class name matches the HTML file name. Concordion finds the HTML document automatically based on the class location.

Concordion Commands

Concordion provides a small, focused set of commands:

concordion:set — assigns a value to a variable:

<span concordion:set="#username">alice</span>

concordion:assertEquals — calls a fixture method and asserts the result:

<span concordion:assertEquals="greet(#username)">Hello, alice!</span>

concordion:execute — calls a fixture method without asserting the return value:

<span concordion:execute="login(#username, #password)"/>

concordion:assertTrue / concordion:assertFalse — asserts a boolean:

<span concordion:assertTrue="isLoggedIn()">the user is logged in</span>

concordion:verifyRows — validates a table of expected results:

<table concordion:verifyRows="#order : getOrders()">
  <tr>
    <th concordion:assertEquals="#order.id">Order ID</th>
    <th concordion:assertEquals="#order.total">Total</th>
  </tr>
</table>

Table-Driven Acceptance Tests

Tables are where Concordion shines. Suppose you're specifying discount calculation rules:

<table concordion:execute="#discount = calculateDiscount(#orderTotal, #customerType)">
  <tr>
    <th concordion:set="#orderTotal">Order Total</th>
    <th concordion:set="#customerType">Customer Type</th>
    <th concordion:assertEquals="#discount">Discount %</th>
  </tr>
  <tr><td>50</td><td>standard</td><td>0</td></tr>
  <tr><td>100</td><td>standard</td><td>5</td></tr>
  <tr><td>100</td><td>premium</td><td>10</td></tr>
  <tr><td>500</td><td>premium</td><td>15</td></tr>
</table>

This table is both documentation and an executable test. Your product team can read it. Your CI pipeline can run it.

Specification Hierarchy with Includes

For large specifications, use the concordion:run command to compose multiple specs:

<!-- Main spec: OrderProcessing.html -->
<a concordion:run="concordion" href="Inventory.html">Inventory management</a>
<a concordion:run="concordion" href="Payment.html">Payment processing</a>
<a concordion:run="concordion" href="Shipping.html">Shipping rules</a>

Each linked spec runs independently. Failures in sub-specs show as failures in the parent.

Running Tests and Reading Output

Run tests with Maven:

mvn test

Concordion generates an HTML report in target/concordion/. Open it in a browser. Passing assertions are green, failing ones are red, with full detail on what was expected versus what the fixture returned.

The output HTML is your living documentation — a snapshot of what the system does, verified by tests that just passed.

Best Practices

Keep specifications business-readable. If the HTML reads like a technical spec, rewrite it. Concordion's value is documentation that non-engineers can understand.

Separate fixture logic from production code. Fixtures are test infrastructure — keep them thin. Call production service classes directly; don't put business logic in fixtures.

One concept per specification. A 200-line spec document is too long. Split into multiple files, linked with concordion:run.

Use concordion:set for setup, tables for data-driven cases. Narrative prose handles the happy path; tables handle boundary conditions and variations.

Version control your specs. HTML specifications live in src/test/resources alongside your tests. They change when requirements change — treating them like test code means they stay accurate.

Connecting to Continuous Integration

Concordion tests run as standard JUnit tests. Any CI system that runs JUnit can run Concordion:

# GitHub Actions
- name: Run Concordion tests
  run: mvn test -Dtest="*Fixture"

Publish the Concordion HTML output as a CI artifact for stakeholder review:

- name: Upload Concordion reports
  uses: actions/upload-artifact@v3
  with:
    name: concordion-reports
    path: target/concordion/

Adding Continuous Monitoring

Concordion verifies system behavior through acceptance tests. For ongoing production assurance, pair it with HelpMeTest — which runs your critical user flows 24/7 in plain English without requiring Java or a build pipeline. Use Concordion to specify and verify behavior during development; use HelpMeTest to confirm that behavior stays correct in production.

Summary

Concordion turns HTML specifications into executable acceptance tests. The framework is intentionally minimal — a handful of HTML attributes, a Java fixture class, and a JUnit runner. The output is living documentation: spec documents annotated with pass/fail evidence from real test runs. If you're working in Java and want specifications that non-technical stakeholders can write and read, Concordion is worth adding to your toolkit.

Read more

Start now free