A colorful BDD-style test runner for Java
Spectrum is inspired by the behavior-driven testing frameworks Jasmine, Kiwi, 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.
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("The Value convenience wrapper", () -> {
final Value<Integer> counter = value();
beforeEach(() -> {
counter.value = 0;
});
beforeEach(() -> {
counter.value++;
});
it("lets you work around Java's requirement that closures only use `final` variables", () -> {
counter.value++;
assertThat(counter.value, is(2));
});
it("can optionally have an initial value set", () -> {
final Value<String> name = value("Alice");
assertThat(name.value, is("Alice"));
});
it("has a null value if not specified", () -> {
final Value<String> name = value();
assertThat(name.value, is(nullValue()));
});
});
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()));
});
});
}
}
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();
});
});
});
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();
});
});
});
});
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"));
});
});
Spectrum moving toward a 1.0
release with close alignment to Jasmine's test declaration API. The library already supports a nice subset of those features:
-
describe
-
it
-
beforeEach
/afterEach
-
beforeAll
/afterAll
-
fit
/fdescribe
-
xit
/xdescribe
Unlike some BDD-style frameworks, Spectrum is only a test runner. Assertions, expectations, mocks, and matchers are the purview of other libraries.
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.
- JUnit 4
- Java 8 (for your tests; systems under test can use older versions)
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:0.7.0'
}
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>0.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Yes please! See CONTRIBUTING.md.