diff --git a/README.md b/README.md index 4513ddf..5b74a6a 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,21 @@ public class ExampleSpec {{ }} ``` +## Supported Features + +Spectrum moving toward a `1.0` release with close alignment to Jasmine's API. The library already supports a nice subset of those features: + +- [x] `describe` +- [x] `it` +- [x] `beforeEach` / `afterEach` +- [x] `beforeAll` / `afterAll` +- [x] `fit` / `fdescribe` +- [ ] `xit` / `xdescribe` + +### Non-Features + +Unlike some BDD-style frameworks, Spectrum is _only_ a test runner. Assertions, expectations, mocks, and matchers are the purview of other libraries. + ## Getting Started Spectrum is available as a [package on jCenter](https://bintray.com/greghaskins/maven/Spectrum/view), so make sure you have jCenter declared as a repository in your build config. Future inclusion in Maven Central (see [#12](https://github.com/greghaskins/spectrum/issues/12)) will make this even easier. diff --git a/src/main/java/com/greghaskins/spectrum/Child.java b/src/main/java/com/greghaskins/spectrum/Child.java new file mode 100644 index 0000000..ccd47e4 --- /dev/null +++ b/src/main/java/com/greghaskins/spectrum/Child.java @@ -0,0 +1,16 @@ +package com.greghaskins.spectrum; + +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; + +interface Child { + + Description getDescription(); + + void run(RunNotifier notifier); + + int testCount(); + + void focus(); + +} diff --git a/src/main/java/com/greghaskins/spectrum/Parent.java b/src/main/java/com/greghaskins/spectrum/Parent.java new file mode 100644 index 0000000..7b3aaae --- /dev/null +++ b/src/main/java/com/greghaskins/spectrum/Parent.java @@ -0,0 +1,14 @@ +package com.greghaskins.spectrum; + +interface Parent { + + void focus(Child child); + + static final Parent NONE = new Parent() { + + @Override + public void focus(final Child child) { + } + }; + +} diff --git a/src/main/java/com/greghaskins/spectrum/Spec.java b/src/main/java/com/greghaskins/spectrum/Spec.java index b3a965a..6b659c0 100644 --- a/src/main/java/com/greghaskins/spectrum/Spec.java +++ b/src/main/java/com/greghaskins/spectrum/Spec.java @@ -1,21 +1,22 @@ package com.greghaskins.spectrum; import org.junit.runner.Description; -import org.junit.runner.Runner; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import com.greghaskins.spectrum.Spectrum.Block; -class Spec extends Runner { +class Spec implements Child { private final Block block; private final Description description; + private final Parent parent; - public Spec(final Description description, final Block block) { + public Spec(final Description description, final Block block, final Parent parent) { this.description = description; this.block = block; + this.parent = parent; } @Override @@ -40,4 +41,9 @@ public int testCount() { return 1; } + @Override + public void focus() { + this.parent.focus(this); + } + } diff --git a/src/main/java/com/greghaskins/spectrum/Spectrum.java b/src/main/java/com/greghaskins/spectrum/Spectrum.java index d79dfcf..77a3aa0 100644 --- a/src/main/java/com/greghaskins/spectrum/Spectrum.java +++ b/src/main/java/com/greghaskins/spectrum/Spectrum.java @@ -37,6 +37,23 @@ public static void describe(final String context, final Block block) { beginDefintion(suite, block); } + /** + * Focus on this specific suite, while ignoring others. + * + * @param context + * Description of the context for this suite + * @param block + * {@link Block} with one or more calls to {@link #it(String, Block) it} that define each expected behavior + * + * @see #describe(String, Block) + * + */ + public static void fdescribe(final String context, final Block block) { + final Suite suite = getCurrentSuite().addSuite(context); + suite.focus(); + beginDefintion(suite, block); + } + /** * Declare a spec, or test, for an expected behavior of the system in this suite context. * @@ -50,6 +67,21 @@ public static void it(final String behavior, final Block block) { getCurrentSuite().addSpec(behavior, block); } + /** + * Focus on this specific spec, while ignoring others. + * + * @param behavior + * Description of the expected behavior + * @param block + * {@link Block} that verifies the system behaves as expected and throws a {@link java.lang.Throwable Throwable} + * if that expectation is not met. + * + * @see #it(String, Block) + */ + public static void fit(final String behavior, final Block block) { + getCurrentSuite().addSpec(behavior, block).focus(); + } + /** * Declare a {@link Block} to be run before each spec in the suite. * @@ -132,7 +164,7 @@ private Value(final T value) { public Spectrum(final Class testClass) { final Description description = Description.createSuiteDescription(testClass); - this.rootSuite = new Suite(description); + this.rootSuite = Suite.rootSuite(description); beginDefintion(this.rootSuite, new ConstructorBlock(testClass)); } diff --git a/src/main/java/com/greghaskins/spectrum/Suite.java b/src/main/java/com/greghaskins/spectrum/Suite.java index 6c3271f..733c161 100644 --- a/src/main/java/com/greghaskins/spectrum/Suite.java +++ b/src/main/java/com/greghaskins/spectrum/Suite.java @@ -2,16 +2,17 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.junit.runner.Description; -import org.junit.runner.Runner; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import com.greghaskins.spectrum.Spectrum.Block; -class Suite extends Runner { +class Suite implements Parent, Child { private final CompositeBlock beforeAll = new CompositeBlock(); private final CompositeBlock afterAll = new CompositeBlock(); @@ -19,20 +20,23 @@ class Suite extends Runner { private final CompositeBlock beforeEach = new CompositeBlock(); private final CompositeBlock afterEach = new CompositeBlock(); - private final List children = new ArrayList(); + private final List children = new ArrayList(); + private final Set focusedChildren = new HashSet(); private final Description description; + private final Parent parent; - public Suite(final Description description) { - this.description = description; + public static Suite rootSuite(final Description description) { + return new Suite(description, Parent.NONE); } - public Suite addSuite(final String name) { - return addSuite(Description.createSuiteDescription(name)); + private Suite(final Description description, final Parent parent) { + this.description = description; + this.parent = parent; } - public Suite addSuite(final Description description) { - final Suite suite = new Suite(description); + public Suite addSuite(final String name) { + final Suite suite = new Suite(Description.createSuiteDescription(name), this); suite.beforeAll(this.beforeAll); suite.beforeEach(this.beforeEach); suite.afterEach(this.afterEach); @@ -41,16 +45,19 @@ public Suite addSuite(final Description description) { } public Spec addSpec(final String name, final Block block) { - final CompositeBlock specBlockInContext = new CompositeBlock(Arrays.asList(this.beforeAll, this.beforeEach, block, this.afterEach)); final Description specDescription = Description.createTestDescription(this.description.getClassName(), name); - final Spec spec = new Spec(specDescription, specBlockInContext); + + final CompositeBlock specBlockInContext = new CompositeBlock( + Arrays.asList(this.beforeAll, this.beforeEach, block, this.afterEach)); + + final Spec spec = new Spec(specDescription, specBlockInContext, this); addChild(spec); return spec; } - private void addChild(final Runner runner) { - this.description.addChild(runner.getDescription()); - this.children.add(runner); + private void addChild(final Child child) { + this.description.addChild(child.getDescription()); + this.children.add(child); } public void beforeAll(final Block block) { @@ -69,6 +76,12 @@ public void afterEach(final Block block) { this.afterEach.addBlock(block); } + @Override + public void focus(final Child child) { + this.focusedChildren.add(child); + focus(); + } + @Override public void run(final RunNotifier notifier) { if (this.testCount() == 0) { @@ -81,8 +94,16 @@ public void run(final RunNotifier notifier) { } private void runChildren(final RunNotifier notifier) { - for (final Runner child : this.children) { + for (final Child child : this.children) { + runChild(child, notifier); + } + } + + private void runChild(final Child child, final RunNotifier notifier) { + if (this.focusedChildren.isEmpty() || this.focusedChildren.contains(child)) { child.run(notifier); + } else { + notifier.fireTestIgnored(child.getDescription()); } } @@ -90,7 +111,8 @@ private void runAfterAll(final RunNotifier notifier) { try { this.afterAll.run(); } catch (final Throwable e) { - final Description failureDescription = Description.createTestDescription(this.description.getClassName(), "error in afterAll"); + final Description failureDescription = Description.createTestDescription(this.description.getClassName(), + "error in afterAll"); this.description.addChild(failureDescription); notifier.fireTestFailure(new Failure(failureDescription, e)); } @@ -104,10 +126,15 @@ public Description getDescription() { @Override public int testCount() { int count = 0; - for (final Runner child : this.children) { + for (final Child child : this.children) { count += child.testCount(); } return count; } + @Override + public void focus() { + this.parent.focus(this); + } + } diff --git a/src/test/java/specs/FocusedSpecs.java b/src/test/java/specs/FocusedSpecs.java new file mode 100644 index 0000000..6af5578 --- /dev/null +++ b/src/test/java/specs/FocusedSpecs.java @@ -0,0 +1,158 @@ +package specs; + +import static com.greghaskins.spectrum.Spectrum.describe; +import static com.greghaskins.spectrum.Spectrum.fdescribe; +import static com.greghaskins.spectrum.Spectrum.fit; +import static com.greghaskins.spectrum.Spectrum.it; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.junit.runner.Result; +import org.junit.runner.RunWith; + +import com.greghaskins.spectrum.Spectrum; + +import helpers.SpectrumRunner; + +@RunWith(Spectrum.class) +public class FocusedSpecs {{ + + describe("Focused specs", () -> { + + it("are declared with `fit`", () -> { + final Result result = SpectrumRunner.run(getSuiteWithFocusedSpecs()); + assertThat(result.getFailureCount(), is(0)); + }); + + it("mark siblings as ignored so they don't get forgotten", () -> { + final Result result = SpectrumRunner.run(getSuiteWithFocusedSpecs()); + assertThat(result.getIgnoreCount(), is(1)); + }); + + describe("when nested in a separate suite", () -> { + + it("cause specs in other suites to be ignored", () -> { + final Result result = SpectrumRunner.run(getSuiteWithNestedFocusedSpecs()); + assertThat(result.getFailureCount(), is(0)); + assertThat(result.getIgnoreCount(), is(1)); + }); + }); + + }); + + describe("Focused suites", () -> { + + it("are declared with `fdescribe`", () -> { + final Result result = SpectrumRunner.run(getSuiteWithFocusedSubSuites()); + assertThat(result.getFailureCount(), is(0)); + }); + + it("ignores tests that aren't focused", ()-> { + final Result result = SpectrumRunner.run(getSuiteWithFocusedSubSuites()); + assertThat(result.getIgnoreCount(), is(2)); + }); + + describe("when nested", () -> { + it("cause specs in other suites to be ignored", () -> { + final Result result = SpectrumRunner.run(getSuiteWithNestedFocusedSuites()); + assertThat(result.getFailureCount(), is(0)); + assertThat(result.getIgnoreCount(), is(1)); + }); + }); + + }); + +} +private static Class getSuiteWithFocusedSpecs() { + class Suite {{ + + describe("A spec that", () -> { + + fit("is focused and will run", () -> { + assertThat(true, is(true)); + }); + + it("is not focused and will not run", () -> { + assertThat(true, is(false)); + }); + + }); + }} + + return Suite.class; +} + +private static Class getSuiteWithNestedFocusedSpecs() { + class Suite {{ + + it("should not run because it isn't focused", () -> { + assertThat(true, is(false)); + }); + + describe("a nested context", () -> { + fit("is focused and will run", () -> { + assertThat(true, is(true)); + }); + }); + }} + return Suite.class; +} + +private static Class getSuiteWithFocusedSubSuites() { + class Suite {{ + describe("an unfocused suite", () -> { + it("is ignored", () -> { + assertThat(true, is(false)); + }); + }); + + fdescribe("focused describe", () -> { + it("will run", () -> { + assertThat(true, is(true)); + }); + it("will also run", () -> { + assertThat(true, is(true)); + }); + }); + + fdescribe("another focused describe", () -> { + fit("is focused and will run", () -> { + assertThat(true, is(true)); + }); + it("is not focused and will not run", () -> { + assertThat(false, is(true)); + }); + }); + + }} + return Suite.class; +} + +private static Class getSuiteWithNestedFocusedSuites() { + class Suite {{ + + describe("an unfocused suite", () -> { + it("should not run because it isn't focused", () -> { + assertThat(true, is(false)); + }); + }); + + describe("a nested context", () -> { + + fdescribe("with a focused sub-suite", () -> { + it("is focused and will run", () -> { + assertThat(true, is(true)); + }); + }); + }); + + describe("another nested context", () -> { + fit("with a focused spec", () -> { + assertThat(true, is(true)); + }); + }); + }} + return Suite.class; +} + +}