JavaSpec 2.0

JavaSpec 2.x is a Java library that lets you use lambdas to write specifications that run on the JUnit 5 platform.

JavaSpec

JavaSpec 2 is a library for the Java Virtual Machine that lets you use lambdas to write specifications (unit tests) that run on the JUnit Platform. Specifications run anywhere JUnit 5 runs: Gradle, JUnit Platform Console, or your favorite IDE. It does the same thing you can do with JUnit 5, but with a syntax that is more descriptive–and more concise–than its JUnit counterpart.

JavaSpec 2 should work just about anywhere JUnit 5 works. All you have to do is add a compile dependency for the API (providing the new spec syntax) and a runtime dependency for a JUnit Test Engine that knows how to turn specifications into JUnit tests.

TL;DR - it’s kind of like the syntax from Jest, Mocha, and Jasmine, but for Java.

Note that this documentation is for the new version of JavaSpec. It uses a different syntax than JavaSpec 1.x.

Build Status

What does JavaSpec do?

JavaSpec helps you take a JUnit test that looks like this…

class GreeterTest {
  @Nested
  @DisplayName("#greet")
  class greet {
    @Test
    @DisplayName("greets the world, given no name")
    void givenNoNameGreetsTheWorld() {
      Greeter subject = new Greeter();
      assertEquals("Hello world!", subject.greet());
    }

    @Test
    @DisplayName("greets a person by name, given a name")
    void givenANameGreetsThePersonByName() {
      Greeter subject = new Greeter();
      assertEquals("Hello Adventurer!", subject.greet("Adventurer"));
    }
  }
}

…and declare it with lambdas instead:

@Testable
public class GreeterSpecs implements SpecClass {
  public void declareSpecs(JavaSpec javaspec) {
    javaspec.describe(Greeter.class, () -> {
      javaspec.describe("#greet", () -> {
        javaspec.it("greets the world, given no name", () -> {
          Greeter subject = new Greeter();
          assertEquals("Hello world!", subject.greet());
        });

        javaspec.it("greets a person by name, given a name", () -> {
          Greeter subject = new Greeter();
          assertEquals("Hello Adventurer!", subject.greet("Adventurer"));
        });
      });
    });
  }
}

This results in test output that looks like this:

Greeter

  #greet

    ✔ greets the world, given no name
    ✔ greets a person by name, given a name

Using this syntax, you can describe behavior with plain language without having to add extra decorators or name tests twice (one machine-readable method name and one human readable @DisplayName).

If you’re into testing, like being descriptive, and don’t mind lambdas: this might be the testing library for you.

Getting Started

Add dependencies

To start using JavaSpec, add the following dependencies:

  1. testImplementation 'info.javaspec:javaspec-api': the syntax you need to declare specs. This needs to be on the classpath you use for compiling test sources and on the one you use when running tests.
  2. testRuntimeOnly 'info.javaspec:javaspec-engine': the TestEngine that runs specs. This only needs to be on the classpath you use at runtime when running tests.
  3. some kind of library for assertions. For example: testImplementation 'org.junit.jupiter:junit-jupiter-api'

In Gradle, that means adding the following to your build.gradle file:

//build.gradle
dependencies {
  //Add these dependencies for JavaSpec
  testImplementation 'info.javaspec:javaspec-api:2.0.0'
  testRuntimeOnly 'info.javaspec:javaspec-engine:2.0.0'

  //Add an assertion library (JUnit 5's assertions shown here)
  testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
}

Maven users add the same dependencies this way:

<dependency>
  <groupId>info.javaspec</groupId>
  <artifactId>javaspec-api</artifactId>
  <version>2.0.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>info.javaspec</groupId>
  <artifactId>javaspec-engine</artifactId>
  <version>2.0.0</version>
  <!-- Not needed to compile tests, but other scopes lead to transitive
  dependencies in consumers: https://stackoverflow.com/a/27729783/112682 -->
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-api</artifactId>
  <version>5.8.2</version>
  <scope>test</scope>
</dependency>

Write Specs

Start writing specs with JavaSpec the same way you would with JUnit: by making a new class. It is often helpful for the name of that class to end in something like Specs, but JavaSpec does not require following any particular convention.

Once you have your new spec class:

  1. Implement SpecClass (info.javaspec:javaspec-api) and #declareSpecs(JavaSpec).
  2. Inside declareSpecs, call JavaSpec#describe with:
    1. some description of what you are testing (or the class itself)
    2. a lambda to hold all the specifications for what you’re describing
  3. Inside the describe lambda, call JavaSpec#it with:
    1. some description of the expected behavior
    2. a lambda with Arrange Act Assert in it, like in any other test

Put it all together, and a basic spec class looks something like this:

import info.javaspec.api.JavaSpec;
import info.javaspec.api.SpecClass;

public class GreeterSpecs implements SpecClass {
  public void declareSpecs(JavaSpec javaspec) {
    javaspec.describe(Greeter.class, () -> {
      javaspec.describe("#greet", () -> {
        javaspec.it("greets the world, given no name", () -> {
          Greeter subject = new Greeter();
          assertEquals("Hello world!", subject.greet());
        });
      });
    });
  }
}

Run specs with Gradle

Once you have the right dependencies, you need a way to run specs on the JUnit Platform. This section describes how to do that in a Gradle project.

As with regular JUnit tests, you still need to add this to your build.gradle:

//build.gradle
test {
  useJUnitPlatform()
}

Then it’s ./gradlew test to run specs, like usual.

For extra-pretty console output, try adding the Gradle Test Logger Plugin with the mocha theme.

Run specs in your IDE

If you have an IDE that can already run JUnit 5 tests, there’s a good chance that it can also run JavaSpec by following these steps:

  1. Make sure you have added the dependencies listed in Add Dependencies.
  2. Add the JUnit Platform Commons dependency: testImplementation 'org.junit.platform:junit-platform-commons:<version>'
  3. Add @Testable to each SpecClass that contains specifications, as a hint to your IDE that this class contains some sort of tests that run on a TestEngine.

This is usually enough for your IDE to indicate that it can run tests in a class, once it has had time to download any new dependencies and index your sources.

For example:

import org.junit.platform.commons.annotation.Testable;

@Testable //Add this IDE hint
public class GreeterSpecs implements SpecClass {
  public void declareSpecs(JavaSpec javaspec) {
    javaspec.describe(Greeter.class, () -> {
      ...
    });
  }
}

Run specs with JUnit Platform Console

Since this is just another TestEngine for the JUnit Platform, you can also run specs on the JUnit Platform Console as seen in this shell snippet:

junit_console_jar='junit-platform-console-standalone-1.8.1.jar'
java -jar "$junit_console_jar" \
  --classpath=info.javaspec.javaspec-api-2.0.0.jar \
  --classpath=<compiled production code and its dependencies> \
  --classpath=<compiled specs and their dependencies> \
  --classpath=info.javaspec.javaspec-engine-2.0.0.jar \
  --include-engine=javaspec-engine \
  ...

Specifically, this means running passing the following arguments to JUnit Platform Console, on top of whichever options you are already using:

More helpful syntax

The JavaSpec API supports a few more things that developers often need to do while testing:

The API also has a variety of ways to help you organize your specs:

Note that these containers exist simply to help you be as descriptive and organized as you need to be. Try to use them judiciously to enhance human readability.

Support

Feel free to file an Issue on Github if you have any questions about using JavaSpec, or if something is not working the way you expected.

Known Issues