Skip to content

A BDD-style test runner for Java 8. Inspired by Jasmine, RSpec, and Cucumber.

License

Notifications You must be signed in to change notification settings

greghaskins/spectrum

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spectrum

Build Status Coverage Status MIT License Download

A colorful BDD-style test runner for Java

Spectrum is inspired by the behavior-driven testing frameworks Jasmine and RSpec, bringing their expressive syntax and functional style to Java tests. It is a custom runner for JUnit, so it works with many development and reporting tools out of the box.

Example

from ExampleSpecs.java

@RunWith(Spectrum.class)
public class ExampleSpecs {
  {

    describe("A spec", () -> {

      final int foo = 1;

      it("is just a code block that verifies something", () -> {
        assertEquals(1, foo);
      });

      it("can use any assertion library you like", () -> {
        org.junit.Assert.assertEquals(1, foo);
        org.hamcrest.MatcherAssert.assertThat(true, is(true));
      });

      describe("nested inside a second describe", () -> {

        final int bar = 1;

        it("can reference both scopes as needed", () -> {
          assertThat(bar, is(equalTo(foo)));
        });

      });

      it("can have `it`s and `describe`s in any order", () -> {
        assertThat(foo, is(1));
      });

    });

    describe("A suite using beforeEach and afterEach", () -> {

      final List<String> items = new ArrayList<>();

      beforeEach(() -> {
        items.add("foo");
      });

      beforeEach(() -> {
        items.add("bar");
      });

      afterEach(() -> {
        items.clear();
      });

      it("runs the beforeEach() blocks in order", () -> {
        assertThat(items, contains("foo", "bar"));
        items.add("bogus");
      });

      it("runs them before every spec", () -> {
        assertThat(items, contains("foo", "bar"));
        items.add("bogus");
      });

      it("runs afterEach after every spec", () -> {
        assertThat(items, not(contains("bogus")));
      });

      describe("when nested", () -> {

        beforeEach(() -> {
          items.add("baz");
        });

        it("runs beforeEach and afterEach from inner and outer scopes", () -> {
          assertThat(items, contains("foo", "bar", "baz"));
        });

      });

    });

    describe("A suite using beforeAll", () -> {

      final List<Integer> numbers = new ArrayList<>();

      beforeAll(() -> {
        numbers.add(1);
      });

      it("sets the initial state before any specs run", () -> {
        assertThat(numbers, contains(1));
        numbers.add(2);
      });

      describe("and afterAll", () -> {

        afterAll(() -> {
          numbers.clear();
        });

        it("does not reset anything between tests", () -> {
          assertThat(numbers, contains(1, 2));
          numbers.add(3);
        });

        it("so proceed with caution; this *will* leak shared state across specs", () -> {
          assertThat(numbers, contains(1, 2, 3));
        });
      });

      it("cleans up after running all specs in the describe block", () -> {
        assertThat(numbers, is(empty()));
      });

    });

  }
}

Screenshot

Spectrum with Eclipse via JUnit

Focused Specs

You can focus the runner on particular spec with fit or a suite with fdescribe so that only those specs get executed.

from FocusedSpecs.java

describe("Focused specs", () -> {

  fit("is focused and will run", () -> {
    assertThat(true, is(true));
  });

  it("is not focused and will not run", () -> {
    throw new Exception();
  });

  fdescribe("a focused suite", () -> {

    it("will run", () -> {
      assertThat(true, is(true));
    });

    it("all its specs", () -> {
      assertThat(true, is(true));
    });
  });

  fdescribe("another focused suite, with focused and unfocused specs", () -> {

    fit("will run focused specs", () -> {
      assertThat(true, is(true));
    });

    it("ignores unfocused specs", () -> {
      throw new Exception();
    });
  });
});

Ignored Specs

You can ignore a spec with xit or ignore all the specs in a suite with xdescribe.

from IgnoredSpecs.java

describe("Ignored specs", () -> {

    xit("is ignored and will not run", () -> {
        throw new Exception();
    });

    it("is not ignored and will run", () -> {
        assertThat(true, is(true));
    });

    xdescribe("an ignored suite", () -> {

        it("will not run", () -> {
            throw new Exception();
        });

        describe("with nesting", () -> {
            it("all its specs", () -> {
                throw new Exception();
            });

            fit("including focused specs", () -> {
                throw new Exception();
            });
        });
    });
});

Common Variable Initialization

The let helper function makes it easy to initialize common variables that are used in multiple specs. This also helps work around Java's restriction that closures can only reference final variables in the containing scope. Values are cached within a spec, and lazily re-initialized between specs as in RSpec #let.

from LetSpecs.java

describe("The `let` helper function", () -> {

  final Supplier<List<String>> items = let(() -> new ArrayList<>(asList("foo", "bar")));

  it("is a way to supply a value for specs", () -> {
    assertThat(items.get(), contains("foo", "bar"));
  });

  it("caches the value so it doesn't get created multiple times for the same spec", () -> {
    assertThat(items.get(), is(sameInstance(items.get())));

    items.get().add("baz");
    items.get().add("blah");
    assertThat(items.get(), contains("foo", "bar", "baz", "blah"));
  });

  it("creates a fresh value for every spec", () -> {
    assertThat(items.get(), contains("foo", "bar"));
  });
});

Supported Features

The Spectrum API is designed to be familiar to Jasmine and RSpec users, while remaining compatible with JUnit. The features and behavior of those libraries help guide decisions on how Specturm should work, both for common scenarios and edge cases. (See the discussion on #41 for an example of how this factors into design decisions.)

The main functions for defining a test are:

  • describe
  • it
  • beforeEach / afterEach
  • beforeAll / afterAll
  • fit / fdescribe
  • xit / xdescribe
  • let

Spectrum also supports:

  • Unlimited nesting of suites within suites
  • Rigorous error handling and reporting when something unexpected goes wrong
  • Compatibility with existing JUnit tools; no configuration required
  • Mixing Spectrum tests and normal JUnit tests in the same project suite

Non-Features

Unlike some BDD-style frameworks, Spectrum is only a test runner. Assertions, expectations, mocks, and matchers are the purview of other libraries such as Hamcrest, AssertJ, Mockito, or plain JUnit.

Getting Started

Spectrum is available as a package on jCenter, so make sure you have jCenter declared as a repository in your build config. Future inclusion in Maven Central (see #12) will make this even easier.

Dependencies

  • JUnit 4
  • Java 8 (for your tests; systems under test can use older versions)

Gradle

Make sure you have the jCenter repository in your init script or project build.gradle:

repositories {
    jcenter()
}

Then add the Spectrum dependency for your tests:

dependencies {
  testCompile 'com.greghaskins:spectrum:1.0.0'
}

Maven

Make sure you have the jCenter repository in your global settings.xml or project pom.xml:

<repositories>
    <repository>
        <id>jcenter</id>
        <url>http://jcenter.bintray.com</url>
    </repository>
</repositories>

Then add Spectrum as a dependency with test scope in your pom.xml:

<project>
  <dependencies>
    <dependency>
      <groupId>com.greghaskins</groupId>
      <artifactId>spectrum</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Can I Contribute?

Yes please! See CONTRIBUTING.md.