├── .codecov.yml ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── config ├── eclipse-java-google-style.xml ├── google_checks.xml ├── intellij-java-google-style.xml ├── spectrum.cleanup.xml └── spectrum.importorder ├── docs ├── Configuration.md ├── FocusingAndIgnoring.md ├── GherkinDSL.md ├── JunitRules.md ├── QuickstartWalkthrough.md ├── README.md ├── SpecificationDSL.md ├── Timeout.md ├── VariablesAndValues.md └── junit-screenshot.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── regression ├── .gitignore └── build.gradle ├── settings.gradle └── src ├── main └── java │ └── com │ └── greghaskins │ └── spectrum │ ├── Block.java │ ├── BlockConfigurationChain.java │ ├── Configure.java │ ├── FilterConfigurationChain.java │ ├── ParameterizedBlock.java │ ├── Spectrum.java │ ├── ThrowingConsumer.java │ ├── ThrowingSupplier.java │ ├── Variable.java │ ├── dsl │ ├── gherkin │ │ ├── Examples.java │ │ ├── Gherkin.java │ │ └── TableRow.java │ └── specification │ │ └── Specification.java │ └── internal │ ├── Child.java │ ├── CompositeTest.java │ ├── DeclarationState.java │ ├── FailureDetectingRunDecorator.java │ ├── LeafChild.java │ ├── NameSanitiser.java │ ├── Parent.java │ ├── RunReporting.java │ ├── Spec.java │ ├── Suite.java │ ├── blocks │ ├── ConstructorBlock.java │ ├── IdempotentBlock.java │ └── NotifyingBlock.java │ ├── configuration │ ├── BlockConfigurable.java │ ├── BlockConfiguration.java │ ├── BlockFocused.java │ ├── BlockIgnore.java │ ├── BlockTagging.java │ ├── BlockTimeout.java │ ├── ConfiguredBlock.java │ ├── ExcludeTags.java │ ├── IncludeTags.java │ ├── SuiteConfigurable.java │ └── TaggingFilterCriteria.java │ ├── hooks │ ├── AbstractSupplyingHook.java │ ├── AfterHook.java │ ├── BeforeHook.java │ ├── EagerLetHook.java │ ├── Hook.java │ ├── HookContext.java │ ├── Hooks.java │ ├── LetHook.java │ ├── NonReportingHook.java │ └── SupplyingHook.java │ └── junit │ ├── RuleContext.java │ ├── Rules.java │ ├── RunNotifierReporting.java │ ├── StubJUnitFrameworkMethod.java │ └── TimeoutWrapper.java └── test └── java ├── com └── greghaskins │ └── spectrum │ ├── ParameterizedVariants.java │ ├── SpectrumHelper.java │ ├── internal │ └── junit │ │ └── RunNotifierReportingTest.java │ └── model │ └── HookContextTest.java ├── given ├── a │ └── spec │ │ └── with │ │ ├── bdd │ │ └── annotation │ │ │ ├── WhenDescribingTheSpec.java │ │ │ └── WhenRunningTheSpec.java │ │ ├── constructor │ │ ├── exception │ │ └── in │ │ │ ├── aftereach │ │ │ └── block │ │ │ │ ├── Fixture.java │ │ │ │ ├── WhenDescribingTheSpec.java │ │ │ │ └── WhenRunningTheSpec.java │ │ │ ├── beforeeach │ │ │ └── block │ │ │ │ └── and │ │ │ │ └── aftereach │ │ │ │ └── block │ │ │ │ ├── Fixture.java │ │ │ │ ├── WhenDescribingTheSpec.java │ │ │ │ └── WhenRunningTheSpec.java │ │ │ ├── constructor │ │ │ └── describe │ │ │ └── block │ │ │ ├── Fixture.java │ │ │ ├── WhenDescribingTheSpec.java │ │ │ └── WhenRunningTheSpec.java │ │ ├── naming │ │ └── problems │ │ │ └── WhenRunningTheSpec.java │ │ ├── nested │ │ └── describe │ │ │ └── blocks │ │ │ └── WhenDescribingTheSpec.java │ │ ├── one │ │ └── passing │ │ │ └── test │ │ │ ├── Fixture.java │ │ │ ├── WhenDescribingTheSpec.java │ │ │ └── WhenRunningTheSpec.java │ │ └── passing │ │ └── and │ │ └── failing │ │ └── tests │ │ ├── Fixture.java │ │ ├── WhenDescribingTheSpec.java │ │ ├── WhenRunningTheSpec.java │ │ └── WhenRunningTheTests.java ├── an │ └── empty │ │ └── spec │ │ ├── Fixture.java │ │ └── WhenRunningTheSpec.java └── implementation │ └── of │ └── junit │ └── StubJUnitFrameworkMethod.java ├── junit ├── RunNotifierTest.java ├── rule │ ├── ExampleMethodRule.java │ └── ExampleRule.java └── spring │ ├── SomeComponent.java │ ├── SomeService.java │ ├── SomeServiceImpl.java │ └── SpringConfig.java ├── matchers └── IsFailure.java └── specs ├── AroundSpecs.java ├── BlockConfigurationSpecs.java ├── EagerLetSpecs.java ├── ExampleSpecs.java ├── FixturesSpec.java ├── FocusedSpecs.java ├── GherkinExampleSpecs.java ├── IgnoredSpecs.java ├── JUnitRuleExample.java ├── LetSpecs.java ├── MockitoSpecJUnitStyle.java ├── MockitoSpecWithRuleClasses.java ├── NestingSpec.java ├── ParameterizedExampleSpecs.java ├── PendingSpec.java ├── ReadmeSpecs.java ├── RunnerSpec.java ├── SpringSpecJUnitStyle.java ├── SpringSpecWithRuleClasses.java ├── TaggedSpecs.java ├── TimeoutSpecs.java └── VariableSpecs.java /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 90..100 3 | round: down 4 | precision: 2 5 | status: 6 | project: 7 | default: 8 | target: auto 9 | threshold: 0% 10 | base: auto 11 | patch: 12 | default: 13 | target: 100% 14 | threshold: 0% 15 | base: auto 16 | comment: 17 | layout: "header, diff, tree" 18 | behavior: once 19 | require_changes: true 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.java text eol=lf 2 | *.md text eol=lf 3 | *.gradle text eol=lf 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .gradle 3 | /build 4 | /out 5 | /intTestHomeDir 6 | /subprojects/*/out 7 | /intellij 8 | /buildSrc/lib 9 | /buildSrc/build 10 | /subprojects/*/build 11 | /subprojects/docs/src/samples/*/*/build 12 | /website/build 13 | /website/website.iml 14 | /website/website.ipr 15 | /website/website.iws 16 | /performanceTest/build 17 | /subprojects/*/ide 18 | /*.iml 19 | /*.ipr 20 | /*.iws 21 | /subprojects/*/*.iml 22 | /buildSrc/*.ipr 23 | /buildSrc/*.iws 24 | /buildSrc/*.iml 25 | /buildSrc/out 26 | *.classpath 27 | *.project 28 | *.settings 29 | *.checkstyle 30 | /bin 31 | /subprojects/*/bin 32 | .DS_Store 33 | /performanceTest/lib 34 | .textmate 35 | /incoming-distributions 36 | .idea 37 | *.sublime-* 38 | .nb-gradle 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: java 3 | jdk: 4 | - oraclejdk8 5 | script: 6 | - "./gradlew check" 7 | - echo "Checking to make sure all changes are checked in..." 8 | - test -z "$(git status --porcelain)" 9 | after_success: 10 | - "bash <(curl -s https://codecov.io/bash)" 11 | deploy: 12 | provider: script 13 | script: "./gradlew bintrayUpload" 14 | on: 15 | tags: true 16 | env: 17 | global: 18 | - secure: Ai47GDG9Hv60mIIVqgQbG9CLzftKnAg1J3h762byEk0tMUNNEfbnBHEiiX5YTduT93RF/zvvME9rn+ycvjFA5Q/KGQNLfQHdGMaAP+lfLQeT8FsiomV9QMGDjDYnF1KGXztZHGB3TOPs6KMwSAiO15QXoRQ+8p89UFphVKy1fmU= 19 | - secure: CpKsnXu69uVUOUI9v4NiqpN3TR+/uWeCWjTvblrJDvZPuI/JWFKl/z2MyrgWmwDHi6G3Q0EGHSt+Y9f42jeH0IyGLJLrTc2W1SbjKkYscQGRLIH33MKVO0o30ltUWYnoySBOxs/8LM8MIKhrwtypzu0ewQoAvmteO9Za4RVkKAk= 20 | - secure: cJj9AefRTM04WSxJoin4t+tg4acY+fFY1itnMvo62KuEbQBCqqMBgkFwFpOMYglP3hiqRQfA5iHeKqhlR8nm/t9UkM5IsDQHfllnhWo2gnsScAl1P0779GgmO4y4fRRGjqi05aoeXBrzBNFKsf5oqer6VGpIhOV8/v/I+WWV/aY= 21 | - secure: IkYKuVUHcBapiKs+9+nmVTvo32Owu6HrtwSTxOXY2xujih8T/ACbq9W+GkacuW1POAgBOlEEe/LZtsB3gm0jfFi6mJhEIM8fni+5ubD8L36ePUM3Q0IQ7smN33VrY6FrGgJYSu8gVk5RElytN0SyG6eoqJdf08fVvAW2/NWzl9E= 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to Spectrum 2 | 3 | _Awesome_. Thanks the help – we can use it! This document has a few notes for getting started contributing to the project. 4 | 5 | ## Communication 6 | 7 | Bugs, enhancements, and discussions are all tracked via [GitHub issues](https://github.com/greghaskins/spectrum/issues). Check there to start a conversation or join one in progress. We don't have a mailing list or anything like that yet, so please [search the existing issues](https://github.com/greghaskins/spectrum/issues?utf8=%E2%9C%93&q=) before [creating a new one](https://github.com/greghaskins/spectrum/issues/new). 8 | 9 | The current project maintainer is [@greghaskins](https://github.com/greghaskins). 10 | 11 | ## Project Goals 12 | 13 | Overall, this project seeks to mirror functionality found in BDD-style test runners on other platforms. Specifically: 14 | - Mirror the core [Jasmine](http://jasmine.github.io/) API and terminology as much as possible to make Spectrum easy to grok for polyglots. 15 | - Document features via easy-to-understand examples, written as specs. These example specs serve as a regression suite, but do not replace lower-level unit tests. 16 | - Integrate as nicely as possible with existing JUnit tooling (reports, CI, IDEs, etc.) so developers can add Spectrum tests to an existing codebase and it "just works." 17 | - In contrast to Jasmine, Spectrum is _only_ a test runner. Things like mocks, spies, and assertions need to be provided by other libraries. 18 | 19 | ## Development Workflow 20 | 21 | This project essentially follows the GitHub Flow. See [this overview](https://guides.github.com/introduction/flow/) and check out the [detailed docs](https://help.github.com/categories/collaborating-on-projects-using-issues-and-pull-requests/) if any of the steps below don't make sense. 22 | 23 | 1. [Fork the repository](https://github.com/greghaskins/spectrum/fork) 24 | 2. Clone your repository locally: 25 | 26 | ``` 27 | git clone git@github.com:your-username-here/spectrum.git 28 | cd spectrum/ 29 | ``` 30 | 3. Add the `upstream` remote to get the latest changes 31 | 32 | ``` 33 | git remote add upstream https://github.com/greghaskins/spectrum.git 34 | git pull upstream/master 35 | ``` 36 | 4. Create a feature branch 37 | 38 | ``` 39 | git checkout -b my-descriptive-branch-name 40 | ``` 41 | 5. Write some code (see [some guidelines below](#code-guidelines)) 42 | 6. Run the build 43 | 44 | ```sh 45 | ./gradlew build # on Linux/Mac 46 | gradlew.bat build # on Windows 47 | ``` 48 | 7. Commit your changes ([with a good message](http://chris.beams.io/posts/git-commit/)) 49 | 8. Publish your branch 50 | 51 | ``` 52 | git push origin my-descriptive-branch-name 53 | ``` 54 | 9. Create a [Pull Request](https://help.github.com/articles/using-pull-requests/) 55 | 56 | ## Code Guidelines 57 | 58 | - Use the `gradlew` build script before committing. The command line is the source of truth on [Travis-CI](https://travis-ci.org/greghaskins/spectrum). Each commit should run green. 59 | - You'll need `java` (version 8) and `git` on your system `PATH` 60 | - Write tests for Spectrum using Spectrum. [Dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) helps find bugs and reveal missing features. Put your specs in `src/test/java/specs`. 61 | - All functional and bugfix changes should be [test-driven](https://en.wikipedia.org/wiki/Test-driven_development). 62 | - [Write good commit messages](http://chris.beams.io/posts/git-commit/) 63 | - This project follows [semantic versioning](http://semver.org/). If your change will break backward-compatibility, please clearly indicate that in your pull request. 64 | - Don't add any external dependencies (especially `compile` dependencies). The production code should depend only on `junit` to make integration as easy as possible. 65 | - Use the code formatting and Checkstyle rules in the `config/` folder with your IDE to catch style issues as you go. These are enforced by the Gradle build. 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Gregory Haskins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spectrum 2 | ======== 3 | 4 | [![Build Status](https://img.shields.io/travis/greghaskins/spectrum.svg)](https://travis-ci.org/greghaskins/spectrum) [![Codecov](https://img.shields.io/codecov/c/github/greghaskins/spectrum.svg)](https://codecov.io/gh/greghaskins/spectrum) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Download](https://api.bintray.com/packages/greghaskins/maven/Spectrum/images/download.svg) ](https://bintray.com/greghaskins/maven/Spectrum/_latestVersion) [![Gitter](https://img.shields.io/gitter/room/greghaskins/spectrum.svg)](https://gitter.im/greghaskins/spectrum) 5 | 6 | *A colorful BDD-style test runner for Java* 7 | 8 | [Spectrum](https://github.com/greghaskins/spectrum) is inspired by the behavior-driven testing frameworks [Jasmine](https://jasmine.github.io/) and [RSpec](http://rspec.info/), bringing their expressive syntax and functional style to Java tests. It is a custom runner for [JUnit](http://junit.org/), so it works with many development and reporting tools out of the box. 9 | 10 | ![Spectrum with Eclipse via JUnit](docs/junit-screenshot.png) 11 | 12 | 15 | 16 | ## Getting Started 17 | 18 | Spectrum 1.2.0 is available as a package on [JCenter](https://bintray.com/greghaskins/maven/Spectrum/view) and [Maven Central](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.greghaskins%22%20AND%20a%3A%22spectrum%22). 19 | 20 | - [Quickstart Guide](https://github.com/greghaskins/spectrum/tree/1.2.0/docs/QuickstartWalkthrough.md) 21 | - [Documentation](https://github.com/greghaskins/spectrum/tree/1.2.0/docs) 22 | - [Release Notes](https://github.com/greghaskins/spectrum/releases) 23 | - [Source Code](https://github.com/greghaskins/spectrum/tree/1.2.0) 24 | 25 | ## Examples 26 | 27 | Spectrum supports Specification-style tests similar to [RSpec](http://rspec.info/) and [Jasmine](https://jasmine.github.io/): 28 | 29 | ```java 30 | @RunWith(Spectrum.class) 31 | public class Specs {{ 32 | 33 | describe("A list", () -> { 34 | 35 | List list = new ArrayList<>(); 36 | 37 | afterEach(list::clear); 38 | 39 | it("should be empty by default", () -> { 40 | assertThat(list.size(), is(0)); 41 | }); 42 | 43 | it("should be able to add items", () -> { 44 | list.add("foo"); 45 | list.add("bar"); 46 | 47 | assertThat(list, contains("foo", "bar")); 48 | }); 49 | 50 | }); 51 | }} 52 | ``` 53 | 54 | And also Gherkin-style tests similar to [Cucumber](https://cucumber.io/docs/reference): 55 | 56 | ```java 57 | @RunWith(Spectrum.class) 58 | public class Features {{ 59 | 60 | feature("Lists", () -> { 61 | 62 | scenario("adding items", () -> { 63 | 64 | Variable> list = new Variable<>(); 65 | 66 | given("an empty list", () -> { 67 | list.set(new ArrayList<>()); 68 | }); 69 | 70 | when("you add the item 'foo'", () -> { 71 | list.get().add("foo"); 72 | }); 73 | 74 | and("you add the item 'bar'", () -> { 75 | list.get().add("bar"); 76 | }); 77 | 78 | then("it contains both foo and bar", () -> { 79 | assertThat(list.get(), contains("foo", "bar")); 80 | }); 81 | 82 | }); 83 | 84 | }); 85 | }} 86 | ``` 87 | 88 | For more details and examples, see the [documentation](https://github.com/greghaskins/spectrum/tree/1.2.0/docs). 89 | 90 | ## Can I Contribute? 91 | 92 | Yes please! See [CONTRIBUTING.md](./CONTRIBUTING.md). 93 | 94 | 95 | -------------------------------------------------------------------------------- /config/spectrum.cleanup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /config/spectrum.importorder: -------------------------------------------------------------------------------- 1 | #Organize Import Order 2 | #Sat May 21 17:18:38 EDT 2016 3 | 3=javax 4 | 2=java 5 | 1= 6 | 0=com.greghaskins.spectrum 7 | -------------------------------------------------------------------------------- /docs/Configuration.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | Metadata to influence the execution can be added anywhere in the test hierarchy. Wherever you can add a block containing suites, tests, or a test body, you can also tag that block with configuration to apply from that point in the hierarchy downwards. 4 | 5 | The common use case for this would be selective running, where tagging with `ignore` or `tags` would control which parts of the hierarchy are executed in a given test run. See [selective running](FocusingAndIgnoring.md) for more information. 6 | 7 | ### Adding configuration to blocks 8 | 9 | The basic format is to take the block inside a `describe` or `it` block (and others) and wrap it inside a `with`. E.g. 10 | 11 | ```java 12 | describe("Some parent", () -> { 13 | it("tests something", () -> {}); 14 | }); 15 | 16 | // might be tagged at parent level and become 17 | describe("Some parent", with(focus(), () -> { 18 | it("tests something", () -> {}); 19 | })); 20 | ``` 21 | 22 | Configurations can be chained by using the `and` function, for example this spec has both tags and a timeout: 23 | 24 | ```java 25 | describe("A suite", with(tags("someTag").and(timeout(10, TimeUnit.SECONDS)), () -> { 26 | it("will test something in time", () -> { 27 | ... 28 | }); 29 | })); 30 | ``` 31 | 32 | `and` can be chained further: 33 | 34 | ```java 35 | describe("A suite", with( 36 | tags("someTag") 37 | .and(timeout(ofSeconds(10))) 38 | .and(focus()), 39 | () -> { 40 | ... 41 | } 42 | )); 43 | ``` 44 | 45 | ### Scope of configuration 46 | 47 | A configuration applies to the current node in the hierarchy and its children. With `ignore`, the effect of ignoring a parent is to ignore all children. With `tag`, the tagging is cumulative. With `timeout` the timeout settings in a parent propagate down to all descendants but can be superseded by a child's own configuration. 48 | 49 | The general rule is that the configuration applies to the whole hierarchy and can only be added to or superseded by children that are allowed to execute. 50 | 51 | ### Configurations available 52 | 53 | In addition to: 54 | 55 | - `ignore()` - ignore this test 56 | - `ignore(String reason)` - ignore this test with a reason 57 | - `focus()` - run focused specs only, especially this 58 | - `tags(String ... tags)` - tag the spec with labels 59 | 60 | there is also: 61 | 62 | - `timeout(Duration timeout)` - make the test fail if it takes too long - see [Timeout](Timeout.md) 63 | -------------------------------------------------------------------------------- /docs/GherkinDSL.md: -------------------------------------------------------------------------------- 1 | # Spectrum Gherkin DSL 2 | 3 | Spectrum provides a Gherkin-style test DSL, accessible from the [`Gherkin`](../src/main/java/com/greghaskins/spectrum/dsl/gherkin/Gherkin.java) interface. In this syntax, tests are declared with `feature`, `scenario`, `given`, `when`, `then`, and ... `and`. It is inspired by the [Cucumber family of test runners](https://cucumber.io/docs/reference), however, instead of using step definitions, steps are defined inline in Java code. 4 | 5 | When using the Gherkin DSL, each `given`/`when`/`then` step must pass before the next is run. Note that they must be declared inside a `scenario` block to work correctly. Multiple `scenario` blocks can be defined as part of a `feature`. 6 | 7 | ## API 8 | 9 | - `feature` - declare a feature with the given description 10 | - `scenario` - describe scenario related to that feature 11 | - `given` / `when` / `then` / `and` - define test steps, which are to be executed in the given order 12 | - `scenarioOutline` - parameterized `scenario` declaration with given examples 13 | 14 | ## Simple Examples 15 | 16 | > from [GherkinExampleSpecs.java](../src/test/java/specs/GherkinExampleSpecs.java) 17 | 18 | ```java 19 | feature("Gherkin-like test DSL", () -> { 20 | 21 | scenario("using given-when-then steps", () -> { 22 | final AtomicInteger integer = new AtomicInteger(); 23 | given("we start with a given", () -> { 24 | integer.set(12); 25 | }); 26 | when("we have a when to execute the system", () -> { 27 | integer.incrementAndGet(); 28 | }); 29 | then("we can assert the outcome", () -> { 30 | assertThat(integer.get(), is(13)); 31 | }); 32 | }); 33 | 34 | scenario("using variables within the scenario to pass data between steps", () -> { 35 | final Variable theData = new Variable<>(); 36 | 37 | given("the data is set", () -> { 38 | theData.set("Hello"); 39 | }); 40 | 41 | when("the data is modified", () -> { 42 | theData.set(theData.get() + " world!"); 43 | }); 44 | 45 | then("the data can be seen with the new value", () -> { 46 | assertThat(theData.get(), is("Hello world!")); 47 | }); 48 | 49 | and("the data is still available in subsequent steps", () -> { 50 | assertThat(theData.get(), is("Hello world!")); 51 | }); 52 | }); 53 | 54 | }); 55 | ``` 56 | 57 | ## Scenario Outline - Parameterized Tests 58 | 59 | If you have several similar scenarios with just different input/output data, then the `scenarioOutline` feature is available to parameterize those tests. This feature is inspired by the [Scenario Outline](https://github.com/cucumber/cucumber/wiki/Scenario-Outlines) functionality in Cucumber. 60 | 61 | > from [ParameterizedExampleSpecs.java](src/test/java/specs/ParameterizedExampleSpecs.java) 62 | 63 | ```java 64 | scenarioOutline("Cucumber eating", 65 | (start, eat, remaining) -> { 66 | 67 | Variable me = new Variable<>(); 68 | 69 | given("there are " + start + " cucumbers", () -> { 70 | me.set(new CukeEater(start)); 71 | }); 72 | 73 | when("I eat " + eat + " cucumbers", () -> { 74 | me.get().eatCucumbers(eat); 75 | }); 76 | 77 | then("I should have " + remaining + " cucumbers", () -> { 78 | assertThat(me.get().remainingCucumbers(), is(remaining)); 79 | }); 80 | }, 81 | 82 | withExamples( 83 | example(12, 5, 7), 84 | example(20, 5, 15)) 85 | 86 | ); 87 | 88 | scenarioOutline("Simple calculations", 89 | (expression, expectedResult) -> { 90 | 91 | Variable calculator = new Variable<>(); 92 | Variable result = new Variable<>(); 93 | 94 | given("a calculator", () -> { 95 | calculator.set(new Calculator()); 96 | }); 97 | when("it computes the expression " + expression, () -> { 98 | result.set(calculator.get().compute(expression)); 99 | }); 100 | then("the result is " + expectedResult, () -> { 101 | assertThat(result.get(), is(expectedResult)); 102 | }); 103 | 104 | }, 105 | 106 | withExamples( 107 | example("1 + 1", 2), 108 | example("5 * 9", 45), 109 | example("7 / 2", 3.5) 110 | ) 111 | ); 112 | ``` 113 | 114 | Parameterization involves supplying some examples via the `withExamples` function. These examples are objects with between 1 and 8 values. This allows you to: 115 | 116 | - Provide a list of objects that parameterize your specs 117 | - Provide a table of values which are used as test inputs 118 | 119 | Thanks to Java 8's generic type system, you can use the examples to drive the type of paramaterized block you need to provide. 120 | 121 | E.g. 122 | 123 | ```java 124 | // provides examples of two ints and a String 125 | withExamples( 126 | example(1,2,"12"), 127 | example(2,3,"23") 128 | ) 129 | ``` 130 | 131 | The above expects you to provide a parameterized block to use the parameters which has three parameters, two of which are integer and the other is String. This might formally be declared as: 132 | 133 | ```java 134 | (int i1, int i3, String s) -> { ... } 135 | ``` 136 | 137 | But you don't need to provide the types, so it's more tersely written as: 138 | 139 | ```java 140 | (i1, it2, s) -> { ... } 141 | ``` 142 | 143 | Think of the `withExamples` and `example` syntax as being a table structure with coherent types within each column. Think of the lambda you write to receive those parameters as the table's header. Intuitively you'd expect something like: 144 | 145 | ```java 146 | (num1, num2, num3) -> { ... }, 147 | withExamples( 148 | example(1, 2, 3), 149 | example(2, 3, 4) 150 | ) 151 | ``` 152 | 153 | This is how `scenarioOutline` works. You provide a consuming block to take the values for each example and define specs with those values, then you provide the values as examples. 154 | -------------------------------------------------------------------------------- /docs/QuickstartWalkthrough.md: -------------------------------------------------------------------------------- 1 | # Quickstart Walkthrough 2 | 5 | To write your first Spectrum test, you will need: 6 | 7 | - Java 8 (for your tests; systems under test can use older versions) 8 | - The Gradle/Maven dependency for Spectrum (see below) 9 | 10 | ### Gradle 11 | 12 | Add the Spectrum dependency to your `testCompile` configuration in `build.gradle`: 13 | 14 | ```groovy 15 | dependencies { 16 | testCompile 'com.greghaskins:spectrum:1.2.0' 17 | } 18 | ``` 19 | 20 | ### Maven 21 | 22 | Add Spectrum as a dependency with `test` scope in your `pom.xml`: 23 | 24 | ```xml 25 | 26 | com.greghaskins 27 | spectrum 28 | 1.2.0 29 | test 30 | 31 | ``` 32 | 33 | ## The Basics 34 | 35 | A Spectrum test class uses the Spectrum test runner and has an anonymous constructor within which the suites and specs will be written: 36 | 37 | ```java 38 | @RunWith(Spectrum.class) 39 | public class MySpecs {{ 40 | // note the extra braces - the Java anonymous constructor 41 | }} 42 | ``` 43 | 44 | The above class is marked to run with the `Spectrum` class, so is executed by JUnit using Spectrum to find and execute all tests. 45 | 46 | When Spectrum is asked to find the tests (specs in this case), it makes an instance of the class, and the body of the anonymous constructor will contain calls to Spectrum methods that describe the suites and specs. 47 | 48 | ## Adding a suite 49 | 50 | To add a suite, we use the `describe` function which takes a `Block` which is a void lambda expression. 51 | 52 | ```java 53 | @RunWith(Spectrum.class) 54 | public class MySpecs {{ 55 | describe("The quick start", () -> { 56 | // inside this block we can put other suites, or individual specs 57 | }); 58 | }} 59 | ``` 60 | 61 | As the `describe` function refers to a suite, its lambda is executed at test definition time to discover any nested suites or nested specs. 62 | 63 | ## Adding a spec 64 | 65 | Specs are meant to describe the behaviour of the system, with bodies that verify that behaviour. They use the `it` keyword and it is conventional to make the description of the spec read as though _it_ were the first word. 66 | 67 | `it` specs also take a block, which represents the execution code for testing that spec. 68 | 69 | ```java 70 | @RunWith(Spectrum.class) 71 | public class MySpecs {{ 72 | describe("The quick start", () -> { 73 | it("can make assertions", () -> { 74 | // here is where you exercise your system-under-test 75 | assertTrue(true); 76 | }); 77 | }); 78 | }} 79 | ``` 80 | 81 | ## Set-up 82 | 83 | If you want to start with a fresh object for every run of your test, then the easiest set-up technique is to use `let`. The `let` function returns a `Supplier` which will contain a freshly built object (according to your definition) within each `it` that uses it: 84 | 85 | ```java 86 | @RunWith(Spectrum.class) 87 | public class MySpecs {{ 88 | describe("The quick start", () -> { 89 | // have a fresh foo for each run 90 | Supplier foo = let(() -> FooFactory.makeNewFoo()); 91 | 92 | it("can make assertions", () -> { 93 | // using get on the supplier, will give you 94 | // this spec's copy of the object 95 | assertTrue(foo.get().isFighter()); 96 | }); 97 | }); 98 | }} 99 | ``` 100 | 101 | ## Where next? 102 | 103 | For more information see the examples and features described the [full documentation](README.md). 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Spectrum Documentation 2 | 3 | Specrtrum is a BDD-style test runner for Java 8. It leverages lambda functions to provide a test-writing experience that is familiar to users of BDD tools from other platforms like Ruby and JavaScript. The library is implemented as a custom runner for JUnit 4, allowing Spectrum tests to integrate (mostly) seamlessly with existing IDEs and tooling. 4 | 5 | ## Supported Features 6 | 7 | Spectrum provides two ways of writing tests: 8 | 9 | - [Specification-style test DSL](SpecificationDSL.md) with `describe` / `it` / `beforeEach` / etc. 10 | - [Gherkin-style test DSL](GherkinDSL.md) with `feature` / `scenario` / `given` / `when` / `then` / etc. 11 | 12 | Spectrum also supports: 13 | 14 | - Unlimited nesting of suites within suites 15 | - Rigorous error handling and reporting when something unexpected goes wrong 16 | - Compatibility with most existing JUnit tools; no configuration required 17 | - Plugging in familiar JUnit-friendly libraries like `MockitoJUnit` or `SpringJUnit` [via JUnit `@Rule`s handling](JunitRules.md). 18 | - Tagging specs for [selective running](FocusingAndIgnoring.md) or adding [configuration](Configuration.md) including [timeouts](Timeout.md) 19 | - Mixing Spectrum tests and normal JUnit tests in the same project suite 20 | - RSpec-style `aroundEach` and `aroundAll` hooks for advanced users and plugin authors 21 | 22 | ## Non-Features 23 | 24 | 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](http://hamcrest.org/JavaHamcrest/), [AssertJ](http://joel-costigliola.github.io/assertj/), [Mockito](http://mockito.org/), or [plain JUnit](https://github.com/junit-team/junit4/wiki/Assertions). 25 | 26 | ## Getting Started 27 | 28 | See the [quickstart walkthrough](QuickstartWalkthrough.md) and the docs for writing [Specification-style](SpecificationDSL.md) or [Gherkin-style](GherkinDSL.md) tests. 29 | -------------------------------------------------------------------------------- /docs/SpecificationDSL.md: -------------------------------------------------------------------------------- 1 | # Spectrum Specification DSL 2 | 3 | Spectrum provides a Specification-style test DSL similar to [RSpec](http://rspec.info/) and [Jasmine](https://jasmine.github.io/), accessible from the [`Specification`](../src/main/java/com/greghaskins/spectrum/dsl/specification/Specification.java) interface. The API is designed to be familiar to users of those tools, while remaining compatible with JUnit. The features and behavior of those libraries help guide decisions on how Spectrum should work, both for common scenarios and edge cases. (See [the discussion on #41](https://github.com/greghaskins/spectrum/pull/41#issuecomment-238729178) for an example of how this factors into design decisions.) 4 | 5 | ## API 6 | 7 | - `describe` - a high level suite 8 | - `context` - alias of `describe` for nesting and grouping 9 | - `it` - an individual spec 10 | - `beforeEach` / `afterEach` - per-spec setup/teardown 11 | - `beforeAll` / `afterAll` - per-suite setup/teardown 12 | - `let` / `eagerLet` / `Variable` - [for providing values to tests](VariablesAndValues.md) 13 | - `fit` / `fdescribe` / `fcontext` - [for focusing](FocusingAndIgnoring.md) 14 | - `xit` / `xdescribe` / `xcontext` - [for ignoring](FocusingAndIgnoring.md) 15 | - `with` / `ignore` / `focus` / `tags` - [for tagging blocks with metadata](FocusingAndIgnoring.md) 16 | 17 | ## Examples 18 | 19 | > from [ExampleSpecs.java](../src/test/java/specs/ExampleSpecs.java) 20 | 21 | ```java 22 | @RunWith(Spectrum.class) 23 | public class ExampleSpecs { 24 | { 25 | 26 | describe("A spec", () -> { 27 | 28 | final int foo = 1; 29 | 30 | it("is just a code block that verifies something", () -> { 31 | assertEquals(1, foo); 32 | }); 33 | 34 | it("can use any assertion library you like", () -> { 35 | org.junit.Assert.assertEquals(1, foo); 36 | org.hamcrest.MatcherAssert.assertThat(true, is(true)); 37 | }); 38 | 39 | describe("nested inside a second describe", () -> { 40 | 41 | final int bar = 1; 42 | 43 | it("can reference both scopes as needed", () -> { 44 | assertThat(bar, is(equalTo(foo))); 45 | }); 46 | 47 | }); 48 | 49 | it("can have `it`s and `describe`s in any order", () -> { 50 | assertThat(foo, is(1)); 51 | }); 52 | 53 | }); 54 | 55 | describe("A suite using beforeEach and afterEach", () -> { 56 | 57 | final List items = new ArrayList<>(); 58 | 59 | beforeEach(() -> { 60 | items.add("foo"); 61 | }); 62 | 63 | beforeEach(() -> { 64 | items.add("bar"); 65 | }); 66 | 67 | afterEach(() -> { 68 | items.clear(); 69 | }); 70 | 71 | it("runs the beforeEach() blocks in order", () -> { 72 | assertThat(items, contains("foo", "bar")); 73 | items.add("bogus"); 74 | }); 75 | 76 | it("runs them before every spec", () -> { 77 | assertThat(items, contains("foo", "bar")); 78 | items.add("bogus"); 79 | }); 80 | 81 | it("runs afterEach after every spec", () -> { 82 | assertThat(items, not(contains("bogus"))); 83 | }); 84 | 85 | describe("when nested", () -> { 86 | 87 | beforeEach(() -> { 88 | items.add("baz"); 89 | }); 90 | 91 | it("runs beforeEach and afterEach from inner and outer scopes", () -> { 92 | assertThat(items, contains("foo", "bar", "baz")); 93 | }); 94 | 95 | }); 96 | 97 | }); 98 | 99 | describe("A suite using beforeAll", () -> { 100 | 101 | final List numbers = new ArrayList<>(); 102 | 103 | beforeAll(() -> { 104 | numbers.add(1); 105 | }); 106 | 107 | it("sets the initial state before any specs run", () -> { 108 | assertThat(numbers, contains(1)); 109 | numbers.add(2); 110 | }); 111 | 112 | describe("and afterAll", () -> { 113 | 114 | afterAll(() -> { 115 | numbers.clear(); 116 | }); 117 | 118 | it("does not reset anything between tests", () -> { 119 | assertThat(numbers, contains(1, 2)); 120 | numbers.add(3); 121 | }); 122 | 123 | it("so proceed with caution; this *will* leak shared state across specs", () -> { 124 | assertThat(numbers, contains(1, 2, 3)); 125 | }); 126 | }); 127 | 128 | it("cleans up after running all specs in the describe block", () -> { 129 | assertThat(numbers, is(empty())); 130 | }); 131 | 132 | }); 133 | 134 | } 135 | } 136 | ``` 137 | -------------------------------------------------------------------------------- /docs/Timeout.md: -------------------------------------------------------------------------------- 1 | # Test Timeouts 2 | 3 | ## JUnit Timeouts 4 | 5 | In JUnit, you usually specify test timeout in the `@Test` annotation: 6 | 7 | ```java 8 | @Test(timeout=123) 9 | public void myTest() { 10 | ... 11 | } 12 | 13 | @Test(timeout=123) 14 | public void myOtherTest() { 15 | ... 16 | } 17 | ``` 18 | 19 | There is also a `Timeout` rule - [see here](https://github.com/junit-team/junit4/wiki/timeout-for-tests) for more information. 20 | 21 | ## Timeouts in Spectrum 22 | 23 | Spectrum's timeout can be applied at the level of each _leaf node_ in the hierachy like the above: 24 | 25 | ```java 26 | describe("some suite", () -> { 27 | it("does one thing under timeout", with(timeout(ofMillis(123)), () -> { 28 | ... 29 | })); 30 | 31 | it("does another under timeout", with(timeout(ofMillis(123)), () -> { 32 | ... 33 | })); 34 | 35 | }); 36 | ``` 37 | The timeout comes inside the configuration block using the `with` syntax. The duration of the timeout is a Java 8 `Duration` object, constructed 38 | (as in the above example) using a static method from the `Duration` class like `ofMillis` or `ofSeconds`. 39 | 40 | The major difference between JUnit and Spectrum timeouts is the ability to apply timeout rules for the family of tests inside a `describe` or `context`: 41 | 42 | ```java 43 | // NOTE: the timeout applies to each test, not the sum of all 44 | describe("some suite", with(timeout(ofMillis(123)), () -> { 45 | it("does one thing under timeout", () -> { 46 | ... 47 | }); 48 | 49 | it("does another under timeout", () -> { 50 | ... 51 | }); 52 | 53 | })); 54 | ``` 55 | 56 | > See also [Configuration](Configuration.md) 57 | -------------------------------------------------------------------------------- /docs/VariablesAndValues.md: -------------------------------------------------------------------------------- 1 | ## Variables and Values 2 | 3 | As `Spectrum` is built using Java 8 lambdas, there are some constraints on the passing of values 4 | from one lambda to another. 5 | 6 | Java requires _effectively final_ variables, which means that any value that can be manipulated 7 | within a lambda needs to be boxed. 8 | 9 | In general, tests should not share state, though the `Variable` class allows for that, which helps 10 | when the test is broken into separate steps. 11 | 12 | The `let` function is used to initialise a fresh, isolated, object for each spec. 13 | 14 | ### Common Variable Initialization 15 | #### Let 16 | The `let` helper function makes it easy to initialize common variables that are used in multiple 17 | specs. In standard JUnit you might expect to use the initializer list of the class or a `@Before` 18 | method to achieve the same. As there is no easy way for `beforeAll` or `beforeEach` to instantiate 19 | a value that will be used in the specs, `let` is the tool of choice. 20 | 21 | Values are cached within a spec, and lazily re-initialized between specs as in 22 | [RSpec #let](http://rspec.info/documentation/3.5/rspec-core/RSpec/Core/MemoizedHelpers/ClassMethods.html#let-instance_method). 23 | 24 | > from [LetSpecs.java](../src/test/java/specs/LetSpecs.java) 25 | 26 | ```java 27 | describe("The `let` helper function", () -> { 28 | 29 | final Supplier> items = let(() -> new ArrayList<>(asList("foo", "bar"))); 30 | 31 | it("is a way to supply a value for specs", () -> { 32 | assertThat(items.get(), contains("foo", "bar")); 33 | }); 34 | 35 | it("caches the value so it doesn't get created multiple times for the same spec", () -> { 36 | assertThat(items.get(), is(sameInstance(items.get()))); 37 | 38 | items.get().add("baz"); 39 | items.get().add("blah"); 40 | assertThat(items.get(), contains("foo", "bar", "baz", "blah")); 41 | }); 42 | 43 | it("creates a fresh value for every spec", () -> { 44 | assertThat(items.get(), contains("foo", "bar")); 45 | }); 46 | }); 47 | ``` 48 | 49 | #### Eager Let 50 | If you need to ensure that a value is initialized at the start of a test, you can use the `eagerLet` 51 | helper function, which has the same semantics as `let` but is evaluated prior to `beforeEach`. This 52 | is often useful when you need to initialize values you can use in your `beforeEach` block. The value 53 | is still initialized after any `beforeAll` blocks. 54 | 55 | This is similar to 56 | [RSpec #let!](http://rspec.info/documentation/3.5/rspec-core/RSpec/Core/MemoizedHelpers/ClassMethods.html#let!-instance_method). 57 | 58 | > from [EagerLetSpecs.java](../src/test/java/specs/EagerLetSpecs.java) 59 | 60 | ```java 61 | describe("The `eagerLet` helper function", () -> { 62 | final Supplier> items = eagerLet(() -> new ArrayList<>(asList("foo", "bar"))); 63 | 64 | final Supplier> eagerItemsCopy = eagerLet(() -> new ArrayList<>(items.get())); 65 | 66 | context("when `beforeEach`, `let`, and `eagerLet` are used", () -> { 67 | final Supplier> lazyItemsCopy = 68 | let(() -> new ArrayList<>(items.get())); 69 | 70 | beforeEach(() -> { 71 | // This would throw a NullPointerException if it ran before eagerItems 72 | items.get().add("baz"); 73 | }); 74 | 75 | it("evaluates all `eagerLet` blocks at once", () -> { 76 | assertThat(eagerItemsCopy.get(), contains("foo", "bar")); 77 | }); 78 | 79 | it("evaluates `beforeEach` after `eagerLet`", () -> { 80 | assertThat(items.get(), contains("foo", "bar", "baz")); 81 | }); 82 | 83 | it("evaluates `let` upon first use", () -> { 84 | assertThat(lazyItemsCopy.get(), contains("foo", "bar", "baz")); 85 | }); 86 | }); 87 | 88 | context("when `beforeAll` and `eagerLet` are used", () -> { 89 | beforeAll(() -> { 90 | assertThat(items.get(), is(nullValue())); 91 | assertThat(eagerItemsCopy.get(), is(nullValue())); 92 | }); 93 | 94 | it("evaluates `beforeAll` prior to `eagerLet`", () -> { 95 | assertThat(items.get(), is(not(nullValue()))); 96 | assertThat(eagerItemsCopy.get(), is(not(nullValue()))); 97 | }); 98 | }); 99 | }); 100 | ``` 101 | 102 | #### Variable 103 | For cases where you need to access a shared variable across specs or steps, the `Variable` helper 104 | class provides a simple `get`/`set` interface. This may be required, for example, to initialize 105 | shared state in a `beforeAll` that is used across multiple specs in that suite. Of course, you 106 | should exercise caution when sharing state across tests 107 | 108 | > from [VariableSpecs.java](../src/test/java/specs/VariableSpecs.java) 109 | 110 | ```java 111 | describe("The Variable convenience wrapper", () -> { 112 | 113 | final Variable counter = new Variable<>(); 114 | 115 | beforeAll(() -> { 116 | counter.set(0); 117 | }); 118 | 119 | beforeEach(() -> { 120 | final int previousValue = counter.get(); 121 | counter.set(previousValue + 1); 122 | }); 123 | 124 | it("lets you work around Java's requirement that closures only use `final` variables", () -> { 125 | assertThat(counter.get(), is(1)); 126 | }); 127 | 128 | it("can share values across scopes, so use it carefully", () -> { 129 | assertThat(counter.get(), is(2)); 130 | }); 131 | 132 | it("can optionally have an initial value set", () -> { 133 | final Variable name = new Variable<>("Alice"); 134 | assertThat(name.get(), is("Alice")); 135 | }); 136 | 137 | it("has a null value if not specified", () -> { 138 | final Variable name = new Variable<>(); 139 | assertNull(name.get()); 140 | }); 141 | 142 | }); 143 | ``` 144 | -------------------------------------------------------------------------------- /docs/junit-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghaskins/spectrum/ca1ff628a07c5d37ad3a09947bf73db2d6d2e8bd/docs/junit-screenshot.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | stableVersion = 1.2.0 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghaskins/spectrum/ca1ff628a07c5d37ad3a09947bf73db2d6d2e8bd/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 21 04:30:30 EDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /regression/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .gradle 3 | /build 4 | /out 5 | /intTestHomeDir 6 | /subprojects/*/out 7 | /intellij 8 | /buildSrc/lib 9 | /buildSrc/build 10 | /subprojects/*/build 11 | /subprojects/docs/src/samples/*/*/build 12 | /website/build 13 | /website/website.iml 14 | /website/website.ipr 15 | /website/website.iws 16 | /performanceTest/build 17 | /subprojects/*/ide 18 | /*.iml 19 | /*.ipr 20 | /*.iws 21 | /subprojects/*/*.iml 22 | /buildSrc/*.ipr 23 | /buildSrc/*.iws 24 | /buildSrc/*.iml 25 | /buildSrc/out 26 | *.classpath 27 | *.project 28 | *.settings 29 | *.checkstyle 30 | /bin 31 | /subprojects/*/bin 32 | .DS_Store 33 | /performanceTest/lib 34 | .textmate 35 | /incoming-distributions 36 | .idea 37 | *.sublime-* 38 | .nb-gradle 39 | 40 | src/test/java 41 | -------------------------------------------------------------------------------- /regression/build.gradle: -------------------------------------------------------------------------------- 1 | configurations { 2 | regressionSuite 3 | } 4 | 5 | dependencies { 6 | testCompile rootProject 7 | testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3' 8 | testCompile group: 'org.mockito', name: 'mockito-core', version: '1.10.19' 9 | 10 | regressionSuite group: 'com.greghaskins', name: 'spectrum', version: '1.0.0', classifier: 'tests' 11 | } 12 | 13 | task extractTests(type: Sync) { 14 | dependsOn configurations.regressionSuite 15 | from { 16 | configurations.regressionSuite.collect { zipTree(it) } 17 | } 18 | into "src/test/java" 19 | } 20 | 21 | compileTestJava { 22 | dependsOn extractTests 23 | } 24 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'regression' 2 | 3 | rootProject.name = 'spectrum' 4 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/Block.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | /** 4 | * A generic code block with a {@link #run()} method to perform any action. Usually defined by a 5 | * lambda function. 6 | */ 7 | @FunctionalInterface 8 | public interface Block { 9 | void run() throws Throwable; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/BlockConfigurationChain.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | import com.greghaskins.spectrum.internal.configuration.BlockConfigurable; 4 | import com.greghaskins.spectrum.internal.configuration.BlockConfiguration; 5 | import com.greghaskins.spectrum.internal.configuration.ConfiguredBlock; 6 | 7 | import java.util.stream.Stream; 8 | 9 | /** 10 | * Chainable configuration of a {@link ConfiguredBlock}. 11 | * Use the factory methods in {@link Spectrum} like {@link Configure#ignore()}, 12 | * {@link Configure#focus()} or {@link Configure#tags(String...)} to add configuration 13 | * to a block. The result will be a {@link BlockConfigurationChain}. To add configurations together 14 | * you use {@link BlockConfigurationChain#and(BlockConfigurationChain)}. This is fluent 15 | * so ands can be chained together.

16 | * e.g.
with(ignore().and(tags("a","b","c")).and(tags("d","e","f"), () -> {...})

17 | * See also: {@link Configure#with(BlockConfigurationChain, Block)} 18 | */ 19 | public final class BlockConfigurationChain { 20 | 21 | private BlockConfiguration blockConfiguration = BlockConfiguration.defaultConfiguration(); 22 | 23 | BlockConfigurationChain() {} 24 | 25 | /** 26 | * Fluent call to add a configurable to the configuration. 27 | * @param configurable to add. 28 | * @return this for fluent calling - users will use {@link #and(BlockConfigurationChain)} 29 | */ 30 | BlockConfigurationChain with(BlockConfigurable configurable) { 31 | blockConfiguration.add(configurable); 32 | 33 | return this; 34 | } 35 | 36 | /** 37 | * Add another configuration to the chain. 38 | * @param chain the configurable to add. 39 | * @return this for fluent calls 40 | */ 41 | public BlockConfigurationChain and(BlockConfigurationChain chain) { 42 | chain.getConfigurables().forEach(this::with); 43 | 44 | return this; 45 | } 46 | 47 | BlockConfiguration getBlockConfiguration() { 48 | return this.blockConfiguration; 49 | } 50 | 51 | private Stream> getConfigurables() { 52 | return this.blockConfiguration.getConfigurables(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/FilterConfigurationChain.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | import com.greghaskins.spectrum.internal.Suite; 4 | import com.greghaskins.spectrum.internal.configuration.SuiteConfigurable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public final class FilterConfigurationChain { 10 | 11 | private final List configurables = new ArrayList<>(); 12 | 13 | FilterConfigurationChain(SuiteConfigurable configurable) { 14 | this.configurables.add(configurable); 15 | } 16 | 17 | public FilterConfigurationChain and(FilterConfigurationChain chain) { 18 | this.configurables.addAll(chain.configurables); 19 | 20 | return this; 21 | } 22 | 23 | void applyTo(Suite suite) { 24 | this.configurables.forEach(configurable -> configurable.applyTo(suite)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/ParameterizedBlock.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | import com.greghaskins.spectrum.dsl.gherkin.Gherkin; 4 | 5 | /** 6 | * The common interface of a parameterized definition block. This provides type safety, for example, 7 | * to {@link Gherkin#scenarioOutline} which takes argument blocks as an input. It looks 8 | * similar to Java 8's Consumer. 9 | */ 10 | public interface ParameterizedBlock { 11 | 12 | @FunctionalInterface 13 | interface OneArgBlock extends ParameterizedBlock { 14 | void run(T arg0); 15 | } 16 | 17 | @FunctionalInterface 18 | interface TwoArgBlock extends ParameterizedBlock { 19 | void run(T0 arg0, T1 arg1); 20 | } 21 | 22 | @FunctionalInterface 23 | interface ThreeArgBlock extends ParameterizedBlock { 24 | void run(T0 arg0, T1 arg1, T2 arg2); 25 | } 26 | 27 | @FunctionalInterface 28 | interface FourArgBlock extends ParameterizedBlock { 29 | void run(T0 arg0, T1 arg1, T2 arg2, T3 arg3); 30 | } 31 | 32 | @FunctionalInterface 33 | interface FiveArgBlock extends ParameterizedBlock { 34 | void run(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4); 35 | } 36 | 37 | @FunctionalInterface 38 | interface SixArgBlock extends ParameterizedBlock { 39 | void run(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); 40 | } 41 | 42 | @FunctionalInterface 43 | interface SevenArgBlock extends ParameterizedBlock { 44 | void run(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); 45 | } 46 | 47 | @FunctionalInterface 48 | interface EightArgBlock extends ParameterizedBlock { 49 | void run(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/ThrowingConsumer.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | import java.util.function.Consumer; 4 | 5 | /** 6 | * An operation that accepts one input and returns no result, similar to {@link Consumer}, but may 7 | * optionally throw checked exceptions. Using {@link ThrowingConsumer} is more convenient for lambda 8 | * functions since it requires less exception handling. 9 | * 10 | * @param the type of the input to the operation 11 | */ 12 | public interface ThrowingConsumer { 13 | 14 | /** 15 | * Performs this operation on the given argument, or throws a throwable. 16 | * 17 | * @param object an input argument 18 | * @throws Throwable when something goes wrong 19 | */ 20 | void accept(T object) throws Throwable; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/ThrowingSupplier.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | import java.util.function.Supplier; 4 | 5 | /** 6 | * Supplier of results similar to {@link Supplier}, but may optionally throw checked exceptions. 7 | * Using {@link ThrowingSupplier} is more convenient for lambda functions since it 8 | * requires less exception handling. 9 | * 10 | * @see Supplier 11 | * 12 | * @param The type of result that will be supplied 13 | */ 14 | @FunctionalInterface 15 | public interface ThrowingSupplier extends Supplier { 16 | 17 | /** 18 | * Get a result. 19 | * 20 | * @return a result 21 | * @throws Throwable any uncaught Error or Exception 22 | */ 23 | T getOrThrow() throws Throwable; 24 | 25 | @Override 26 | default T get() { 27 | try { 28 | return getOrThrow(); 29 | } catch (final RuntimeException | Error unchecked) { 30 | throw unchecked; 31 | } catch (final Throwable checked) { 32 | throw new RuntimeException(checked); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/Variable.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | import java.util.function.Supplier; 4 | 5 | /** 6 | * This is a convenience class to make working with Java closures easier. Variables from outer 7 | * scopes must be {@code final} to be referenced inside lambda functions. Wrapping objects in a 8 | * {@link #Variable} instance allows you to get/set values from anywhere as long as the Variable 9 | * itself is {@code final}. 10 | */ 11 | public final class Variable implements Supplier { 12 | 13 | private T value; 14 | 15 | /** 16 | * Create a Variable with a {@code null} initial value. 17 | */ 18 | public Variable() {} 19 | 20 | /** 21 | * Create a Variable with the given initial value. 22 | * 23 | * @param value starting value 24 | */ 25 | public Variable(final T value) { 26 | set(value); 27 | } 28 | 29 | /** 30 | * Get the current value of this Variable. 31 | * 32 | * @return current value 33 | */ 34 | @Override 35 | public T get() { 36 | return this.value; 37 | } 38 | 39 | /** 40 | * Change the value of this Variable. 41 | * 42 | * @param value new value 43 | */ 44 | public void set(final T value) { 45 | this.value = value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/dsl/gherkin/Examples.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.dsl.gherkin; 2 | 3 | import java.util.Collection; 4 | import java.util.stream.Stream; 5 | 6 | public class Examples { 7 | 8 | private final Collection> examples; 9 | 10 | Examples(Collection> examples) { 11 | this.examples = examples; 12 | } 13 | 14 | Stream> rows() { 15 | return this.examples.stream(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/dsl/gherkin/TableRow.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.dsl.gherkin; 2 | 3 | import java.util.Arrays; 4 | import java.util.Optional; 5 | import java.util.function.Consumer; 6 | import java.util.stream.Collectors; 7 | 8 | public class TableRow { 9 | 10 | private final String description; 11 | private final Consumer blockRunner; 12 | 13 | TableRow(Consumer blockRunner, Object... arguments) { 14 | this.blockRunner = blockRunner; 15 | this.description = describe(arguments); 16 | } 17 | 18 | void runDeclaration(T block) { 19 | this.blockRunner.accept(block); 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return this.description; 25 | } 26 | 27 | private static String describe(Object[] objects) { 28 | return Arrays.stream(objects) 29 | .map(o -> Optional.ofNullable(o) 30 | .map(Object::toString) 31 | .orElse("null")) 32 | .collect(Collectors.joining(" | ", "| ", " |")); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/Child.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | import com.greghaskins.spectrum.internal.configuration.ConfiguredBlock; 5 | import com.greghaskins.spectrum.internal.configuration.TaggingFilterCriteria; 6 | import com.greghaskins.spectrum.internal.hooks.Hook; 7 | 8 | import org.junit.runner.Description; 9 | import org.junit.runner.notification.Failure; 10 | 11 | public interface Child { 12 | 13 | Description getDescription(); 14 | 15 | void run(RunReporting reporting); 16 | 17 | int testCount(); 18 | 19 | void focus(); 20 | 21 | void ignore(); 22 | 23 | /** 24 | * Is either this child ignored or are all of its children ignored. 25 | * @return true if nothing will run from here 26 | */ 27 | boolean isEffectivelyIgnored(); 28 | 29 | /** 30 | * Is this child something which runs as a test. 31 | * @return if the child is atomic 32 | */ 33 | default boolean isAtomic() { 34 | return false; 35 | } 36 | 37 | /** 38 | * Does this child appear as an individual test within the test runner. 39 | * @return true if it's an individual test item in the runner 40 | */ 41 | default boolean isLeaf() { 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/CompositeTest.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | import com.greghaskins.spectrum.internal.configuration.TaggingFilterCriteria; 4 | 5 | import org.junit.runner.Description; 6 | import org.junit.runner.notification.Failure; 7 | 8 | /** 9 | * Subclass of {@link Suite} that represent the fact that some tests are composed 10 | * of interrelated steps which add up to a single test. 11 | */ 12 | final class CompositeTest extends Suite { 13 | /** 14 | * Constructs a Composite Test, which is a suite run as an atomic test. 15 | * @param description of the test 16 | * @param parent parent suite 17 | * @param tagging tagging state to inherit from parent 18 | */ 19 | CompositeTest(final Description description, final Parent parent, 20 | final TaggingFilterCriteria tagging) { 21 | super(description, parent, CompositeTest::abortOnFailureChildRunner, tagging); 22 | } 23 | 24 | @Override 25 | public boolean isAtomic() { 26 | return true; 27 | } 28 | 29 | private static void abortOnFailureChildRunner(final Suite suite, 30 | final RunReporting reporting) { 31 | FailureDetectingRunDecorator decoratedReporting = 32 | new FailureDetectingRunDecorator<>(reporting); 33 | for (Child child : suite.children) { 34 | if (decoratedReporting.hasFailedYet()) { 35 | child.ignore(); 36 | } 37 | suite.runChild(child, decoratedReporting); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/DeclarationState.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | import com.greghaskins.spectrum.internal.hooks.Hook; 5 | import com.greghaskins.spectrum.internal.hooks.HookContext; 6 | import com.greghaskins.spectrum.internal.hooks.HookContext.AppliesTo; 7 | import com.greghaskins.spectrum.internal.hooks.HookContext.Precedence; 8 | 9 | import java.util.ArrayDeque; 10 | import java.util.Deque; 11 | 12 | public final class DeclarationState { 13 | 14 | private static final ThreadLocal instance = 15 | ThreadLocal.withInitial(DeclarationState::new); 16 | 17 | public static DeclarationState instance() { 18 | return instance.get(); 19 | } 20 | 21 | private final Deque suiteStack = new ArrayDeque<>(); 22 | 23 | private DeclarationState() {} 24 | 25 | public Suite getCurrentSuiteBeingDeclared() { 26 | return suiteStack.peek(); 27 | } 28 | 29 | private int getCurrentDepth() { 30 | return suiteStack.size(); 31 | } 32 | 33 | public void beginDeclaration(final Suite suite, final Block definitionBlock) { 34 | suiteStack.push(suite); 35 | 36 | try { 37 | definitionBlock.run(); 38 | } catch (final Throwable error) { 39 | suite.removeAllChildren(); 40 | suite.addSpec("encountered an error", () -> { 41 | throw error; 42 | }); 43 | } 44 | suiteStack.pop(); 45 | } 46 | 47 | public void addHook(final Hook hook, final AppliesTo appliesTo, final Precedence precedence) { 48 | addHook(new HookContext(hook, instance().getCurrentDepth(), appliesTo, precedence)); 49 | } 50 | 51 | private void addHook(HookContext hook) { 52 | getCurrentSuiteBeingDeclared().addHook(hook); 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/FailureDetectingRunDecorator.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | /** 4 | * A listener to detect test failure. 5 | */ 6 | public class FailureDetectingRunDecorator implements RunReporting { 7 | private boolean hasFailedYet = false; 8 | 9 | private RunReporting decoratee; 10 | 11 | public FailureDetectingRunDecorator(RunReporting decoratee) { 12 | this.decoratee = decoratee; 13 | } 14 | 15 | /** 16 | * Has the run failed since we've been listening. 17 | * @return whether any previous failures have been reported 18 | */ 19 | public boolean hasFailedYet() { 20 | return hasFailedYet; 21 | } 22 | 23 | @Override 24 | public void fireTestFailure(F failure) { 25 | decoratee.fireTestFailure(failure); 26 | hasFailedYet = true; 27 | } 28 | 29 | @Override 30 | public void fireTestIgnored(T description) { 31 | decoratee.fireTestIgnored(description); 32 | } 33 | 34 | @Override 35 | public void fireTestStarted(T description) { 36 | decoratee.fireTestStarted(description); 37 | } 38 | 39 | @Override 40 | public void fireTestFinished(T description) { 41 | decoratee.fireTestFinished(description); 42 | } 43 | 44 | @Override 45 | public void fireTestAssumptionFailed(F failure) { 46 | decoratee.fireTestAssumptionFailed(failure); 47 | hasFailedYet = true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/LeafChild.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | import com.greghaskins.spectrum.internal.hooks.HookContext; 4 | import com.greghaskins.spectrum.internal.hooks.NonReportingHook; 5 | 6 | /** 7 | * Interface for a child that is also a leaf node in the test hierarchy. 8 | */ 9 | public interface LeafChild extends Child { 10 | /** 11 | * Add an additional hook directly to a leaf of the hierarchy. 12 | * @param leafHook hook to add 13 | * @param precedence precedence, for sorting hooks into order 14 | */ 15 | void addLeafHook(NonReportingHook leafHook, HookContext.Precedence precedence); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/NameSanitiser.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * Sanitises names within a Suite. To stop a name being duplicated or 8 | * containing characters that upset test runners. 9 | */ 10 | public class NameSanitiser { 11 | private Set namesUsed = new HashSet<>(); 12 | 13 | /** 14 | * Deduplicate the given {@code name} and filter out any bad characters. 15 | * 16 | *

Note: this function has side effects - sanitising a name will cause it to be remembered for future 17 | * deduplication purposes. 18 | * 19 | * @param name the spec name 20 | * @return a name unique to this sanitiser which has known bad characters removed. 21 | */ 22 | public String sanitise(final String name) { 23 | String sanitised = name.replaceAll("\\(", "[") 24 | .replaceAll("\\)", "]"); 25 | 26 | sanitised = sanitised.replaceAll("\\.", "_"); 27 | 28 | int suffix = 1; 29 | String deDuplicated = sanitised; 30 | while (this.namesUsed.contains(deDuplicated)) { 31 | deDuplicated = sanitised + "_" + suffix++; 32 | } 33 | this.namesUsed.add(deDuplicated); 34 | 35 | return deDuplicated; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/Parent.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | import com.greghaskins.spectrum.internal.hooks.Hooks; 4 | 5 | public interface Parent { 6 | 7 | void focus(Child child); 8 | 9 | boolean isIgnored(); 10 | 11 | Hooks getInheritableHooks(); 12 | 13 | Parent NONE = new Parent() { 14 | @Override 15 | public void focus(final Child child) {} 16 | 17 | @Override 18 | public boolean isIgnored() { 19 | return false; 20 | } 21 | 22 | @Override 23 | public Hooks getInheritableHooks() { 24 | return new Hooks(); 25 | } 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/RunReporting.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | /** 4 | * Abstraction of reporting that's done on a test run. This 5 | * decouples Spectrum from JUnit and also allows Spectrum to 6 | * report its own way with an adapter for each test framework 7 | * providing the right updates according to the target framework's 8 | * needs/expectations. 9 | */ 10 | public interface RunReporting { 11 | /** 12 | * Marks the test as ignored. 13 | * @param description description of test 14 | */ 15 | void fireTestIgnored(final T description); 16 | 17 | /** 18 | * Markes the test as having started - call this before any test-specific results. 19 | * @param description description of test 20 | */ 21 | void fireTestStarted(final T description); 22 | 23 | /** 24 | * Marks the test as finished - call this after any test-specific results, whether 25 | * passed or failed. 26 | * @param description description of test 27 | */ 28 | void fireTestFinished(final T description); 29 | 30 | /** 31 | * Marks a test as having failed. 32 | * @param failure failure information 33 | */ 34 | void fireTestFailure(final F failure); 35 | 36 | /** 37 | * Marks a test as having an assumption failure. 38 | * @param failure failure information 39 | */ 40 | void fireTestAssumptionFailed(final F failure); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/Spec.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal; 2 | 3 | 4 | import com.greghaskins.spectrum.Block; 5 | import com.greghaskins.spectrum.internal.hooks.Hook; 6 | import com.greghaskins.spectrum.internal.hooks.HookContext; 7 | import com.greghaskins.spectrum.internal.hooks.Hooks; 8 | import com.greghaskins.spectrum.internal.hooks.NonReportingHook; 9 | 10 | import org.junit.runner.Description; 11 | import org.junit.runner.notification.Failure; 12 | 13 | final class Spec implements LeafChild { 14 | 15 | private final Block block; 16 | private final Description description; 17 | private final Parent parent; 18 | private boolean ignored = false; 19 | private Hooks leafHooks = new Hooks(); 20 | 21 | Spec(final Description description, final Block block, final Parent parent) { 22 | this.description = description; 23 | this.block = block; 24 | this.parent = parent; 25 | this.ignored = parent.isIgnored(); 26 | } 27 | 28 | @Override 29 | public Description getDescription() { 30 | return this.description; 31 | } 32 | 33 | @Override 34 | public void run(final RunReporting notifier) { 35 | if (this.ignored) { 36 | notifier.fireTestIgnored(this.description); 37 | return; 38 | } 39 | 40 | // apply leaf hooks around the inner block 41 | leafHooks.sorted().runAround(this.description, notifier, block); 42 | } 43 | 44 | @Override 45 | public int testCount() { 46 | return 1; 47 | } 48 | 49 | @Override 50 | public void focus() { 51 | if (this.ignored) { 52 | return; 53 | } 54 | 55 | this.parent.focus(this); 56 | } 57 | 58 | @Override 59 | public void ignore() { 60 | this.ignored = true; 61 | } 62 | 63 | @Override 64 | public boolean isAtomic() { 65 | return true; 66 | } 67 | 68 | @Override 69 | public boolean isLeaf() { 70 | return true; 71 | } 72 | 73 | @Override 74 | public boolean isEffectivelyIgnored() { 75 | return ignored; 76 | } 77 | 78 | @Override 79 | public void addLeafHook(NonReportingHook leafHook, HookContext.Precedence precedence) { 80 | // hooks at this level are always at the same point in the hierarchy and applying to each child 81 | leafHooks.add(new HookContext(leafHook, 0, HookContext.AppliesTo.EACH_CHILD, precedence)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/blocks/ConstructorBlock.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.blocks; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.util.function.Supplier; 8 | 9 | public final class ConstructorBlock implements Block, Supplier { 10 | 11 | private final Class klass; 12 | private T testObject; 13 | 14 | public ConstructorBlock(final Class klass) { 15 | this.klass = klass; 16 | } 17 | 18 | @Override 19 | public void run() throws Throwable { 20 | try { 21 | final Constructor constructor = this.klass.getDeclaredConstructor(); 22 | constructor.setAccessible(true); 23 | testObject = constructor.newInstance(); 24 | } catch (final InvocationTargetException invocationTargetException) { 25 | throw invocationTargetException.getTargetException(); 26 | } catch (final Exception error) { 27 | throw new UnableToConstructSpecException(this.klass, error); 28 | } 29 | } 30 | 31 | @Override 32 | public T get() { 33 | return testObject; 34 | } 35 | 36 | private static class UnableToConstructSpecException extends RuntimeException { 37 | 38 | private UnableToConstructSpecException(final Class klass, final Throwable cause) { 39 | super(klass.getName(), cause); 40 | } 41 | 42 | private static final long serialVersionUID = 1L; 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/blocks/IdempotentBlock.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.blocks; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | 5 | public final class IdempotentBlock implements Block { 6 | 7 | private final Block block; 8 | private Block result; 9 | 10 | public IdempotentBlock(final Block block) { 11 | this.block = block; 12 | } 13 | 14 | @Override 15 | public void run() throws Throwable { 16 | if (this.result == null) { 17 | this.result = runBlockOnce(this.block); 18 | } 19 | this.result.run(); 20 | } 21 | 22 | private static Block runBlockOnce(final Block block) { 23 | try { 24 | block.run(); 25 | 26 | return alwaysPass(); 27 | } catch (final Throwable error) { 28 | return alwaysFail(error); 29 | } 30 | } 31 | 32 | private static Block alwaysPass() { 33 | return () -> { 34 | }; 35 | } 36 | 37 | private static Block alwaysFail(final Throwable error) { 38 | return () -> { 39 | throw error; 40 | }; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/blocks/NotifyingBlock.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.blocks; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | import com.greghaskins.spectrum.internal.RunReporting; 5 | 6 | import org.junit.AssumptionViolatedException; 7 | import org.junit.runner.Description; 8 | import org.junit.runner.notification.Failure; 9 | import org.junit.runner.notification.RunNotifier; 10 | 11 | /** 12 | * An interface which can be run with a description and notifier. 13 | */ 14 | @FunctionalInterface 15 | public interface NotifyingBlock { 16 | void run(final Description description, final RunReporting reporting); 17 | 18 | /** 19 | * Execute the block notifying the notifier of any errors. 20 | * @param description description to cite to the notifier 21 | * @param reporting object to be informed of assumption or test failure 22 | * @param block block to un 23 | */ 24 | static void run(final Description description, final RunReporting reporting, 25 | final Block block) { 26 | wrap(block).run(description, reporting); 27 | } 28 | 29 | /** 30 | * Convert a {@link Block} into a {@link NotifyingBlock} 31 | * @param block to convert 32 | * @return {@link NotifyingBlock} which can be run with {@link Description} 33 | * and {@link RunNotifier} so that exceptions are reported 34 | * rather than rethrown. 35 | */ 36 | static NotifyingBlock wrap(final Block block) { 37 | return (description, reporting) -> { 38 | try { 39 | executeAndReport(description, reporting, block); 40 | } catch (final Throwable exception) { 41 | // the exception has already been reported and should not be rethrown 42 | } 43 | }; 44 | } 45 | 46 | /** 47 | * Add the reporting capability to a block. The block runs as normal, but 48 | * any exceptions are ALSO reported on the way to outer catch blocks. 49 | * @param description which test called the block 50 | * @param reporting object to inform of failure 51 | * @param block the block to execute 52 | * @return a block which has notification built in 53 | */ 54 | static Block wrapWithReporting(final Description description, 55 | final RunReporting reporting, 56 | final Block block) { 57 | return () -> executeAndReport(description, reporting, block); 58 | } 59 | 60 | /** 61 | * Add notification of exceptions to a throwing block. 62 | * @param description which test called the block 63 | * @param reporting object to inform of failure 64 | * @param block the block to execute 65 | * @throws Throwable the error which was reported to the {@link RunNotifier} 66 | */ 67 | static void executeAndReport(final Description description, 68 | final RunReporting reporting, 69 | final Block block) throws Throwable { 70 | try { 71 | block.run(); 72 | } catch (final AssumptionViolatedException assumptionViolation) { 73 | reporting.fireTestAssumptionFailed(new Failure(description, assumptionViolation)); 74 | throw assumptionViolation; 75 | } catch (final Throwable throwable) { 76 | reporting.fireTestFailure(new Failure(description, throwable)); 77 | throw throwable; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/BlockConfigurable.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import com.greghaskins.spectrum.internal.Child; 4 | 5 | /** 6 | * Defines a configurable thing on a block. 7 | */ 8 | public interface BlockConfigurable { 9 | /** 10 | * Return if this sort of configurable inherited by a child from a suite. 11 | * @return true when the configurable is inheritable. 12 | */ 13 | boolean inheritedByChild(); 14 | 15 | /** 16 | * Modify the child according to this configurable. 17 | * @param child to modify. 18 | * @param state any known tagging filter criteria (used by tagging configurable). 19 | */ 20 | void applyTo(final Child child, final TaggingFilterCriteria state); 21 | 22 | /** 23 | * Provide a merged configurable, based on the combination of this configurable 24 | * and the input. 25 | * @param other the configurable to merge with this. Must be of same type. Can be null. 26 | * @return a new BlockConfigurable of the right type with the contents of both. 27 | */ 28 | BlockConfigurable merge(BlockConfigurable other); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/BlockConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import com.greghaskins.spectrum.internal.Child; 4 | 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.stream.Stream; 9 | 10 | /** 11 | * Configurations that apply to a {@link ConfiguredBlock}. 12 | */ 13 | public class BlockConfiguration { 14 | /** 15 | * Combine provided configuration objects together. 16 | * 17 | * @param conditions to combine 18 | * @return a combination of all configurations as a new object 19 | */ 20 | public static BlockConfiguration merge(BlockConfiguration... conditions) { 21 | BlockConfiguration merged = new BlockConfiguration(); 22 | Arrays.stream(conditions).forEach(merged::mergeWith); 23 | 24 | return merged; 25 | } 26 | 27 | /** 28 | * Configurations stored by type of configurable. 29 | */ 30 | private Map, BlockConfigurable> configurations = new HashMap<>(); 31 | 32 | /** 33 | * Children should inherit tags and ignore status, but not focus. 34 | * 35 | * @return a new BlockConfiguration that would apply for a Child 36 | */ 37 | public BlockConfiguration forChild() { 38 | BlockConfiguration conditions = new BlockConfiguration(); 39 | configurations.values() 40 | .stream() 41 | .filter(BlockConfigurable::inheritedByChild) 42 | .forEach(conditions::add); 43 | 44 | return conditions; 45 | } 46 | 47 | /** 48 | * Add a configurable to the configuration. 49 | * @param configurable to add 50 | */ 51 | public void add(BlockConfigurable configurable) { 52 | // merge this configurable into what we already have 53 | Class configurableClass = configurable.getClass(); 54 | configurations.put(configurableClass, configurable.merge(configurations.get(configurableClass))); 55 | } 56 | 57 | private void mergeWith(BlockConfiguration other) { 58 | other.configurations 59 | .values() 60 | .forEach(this::add); 61 | } 62 | 63 | private BlockConfiguration() { 64 | // there must be default tagging of blank for tagging to work 65 | add(new BlockTagging()); 66 | } 67 | 68 | public static BlockConfiguration defaultConfiguration() { 69 | return new BlockConfiguration(); 70 | } 71 | 72 | /** 73 | * Visitor pattern - when necessary, the child gets the configurations to apply to it. 74 | * 75 | * @param child to be pre-processed according to the configurations. 76 | * @param state the tagging state within which the child is operating 77 | */ 78 | public void applyTo(final Child child, final TaggingFilterCriteria state) { 79 | configurations.values().forEach(configurable -> configurable.applyTo(child, state)); 80 | } 81 | 82 | public Stream> getConfigurables() { 83 | return configurations.values().stream(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/BlockFocused.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import com.greghaskins.spectrum.internal.Child; 4 | 5 | /** 6 | * Created by friezea on 02/03/2017. 7 | */ 8 | public class BlockFocused implements BlockConfigurable { 9 | @Override 10 | public boolean inheritedByChild() { 11 | // focus is not inherited 12 | 13 | return false; 14 | } 15 | 16 | @Override 17 | public void applyTo(Child child, TaggingFilterCriteria state) { 18 | child.focus(); 19 | } 20 | 21 | @Override 22 | public BlockConfigurable merge(BlockConfigurable other) { 23 | // any focusing means future focusing 24 | // so this will always add up to focused, regardless of what other 25 | // may contain 26 | 27 | return new BlockFocused(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/BlockIgnore.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import com.greghaskins.spectrum.internal.Child; 4 | 5 | /** 6 | * A configurable that does ignoring. 7 | */ 8 | public class BlockIgnore implements BlockConfigurable { 9 | @SuppressWarnings("unused") 10 | private String reason; 11 | 12 | public BlockIgnore() {} 13 | 14 | public BlockIgnore(String reason) { 15 | this.reason = reason; 16 | } 17 | 18 | @Override 19 | public boolean inheritedByChild() { 20 | return true; 21 | } 22 | 23 | @Override 24 | public void applyTo(Child child, TaggingFilterCriteria state) { 25 | child.ignore(); 26 | } 27 | 28 | @Override 29 | public BlockConfigurable merge(BlockConfigurable other) { 30 | // any ignoring means future ignoring 31 | // so this will always add up to ignore, regardless of what other 32 | // may contain 33 | 34 | return new BlockIgnore(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/BlockTagging.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import com.greghaskins.spectrum.internal.Child; 4 | 5 | import java.util.Arrays; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | /** 10 | * The tags of a given block. 11 | */ 12 | public class BlockTagging implements BlockConfigurable { 13 | private Set hasTags = new HashSet<>(); 14 | 15 | public BlockTagging(String... tags) { 16 | Arrays.stream(tags).forEach(hasTags::add); 17 | } 18 | 19 | private BlockTagging(Set tags) { 20 | hasTags.addAll(tags); 21 | } 22 | 23 | @Override 24 | public boolean inheritedByChild() { 25 | return true; 26 | } 27 | 28 | @Override 29 | public void applyTo(Child child, TaggingFilterCriteria state) { 30 | if (!state.isAllowedToRun(hasTags)) { 31 | child.ignore(); 32 | } 33 | } 34 | 35 | @Override 36 | public BlockConfigurable merge(BlockConfigurable other) { 37 | BlockTagging merged = new BlockTagging(hasTags); 38 | if (other != null) { 39 | // the downcast is allowed because this is only called with an object 40 | // of the same type as the parent merge routine is working type 41 | // by type 42 | merged.hasTags.addAll(((BlockTagging) other).hasTags); 43 | } 44 | 45 | return merged; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/BlockTimeout.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import static com.greghaskins.spectrum.internal.junit.TimeoutWrapper.timeoutHook; 4 | 5 | import com.greghaskins.spectrum.internal.Child; 6 | import com.greghaskins.spectrum.internal.LeafChild; 7 | import com.greghaskins.spectrum.internal.hooks.HookContext; 8 | 9 | import java.time.Duration; 10 | 11 | /** 12 | * Applies timeout metadata to the block. A timeout will cause a leaf child to 13 | * run within a sentinel which fails the test if it takes too long. 14 | */ 15 | public class BlockTimeout implements BlockConfigurable { 16 | private Duration timeout; 17 | 18 | /** 19 | * Create a timeout. 20 | * @param timeout duration of the timeout 21 | */ 22 | public BlockTimeout(Duration timeout) { 23 | this.timeout = timeout; 24 | } 25 | 26 | @Override 27 | public boolean inheritedByChild() { 28 | return true; 29 | } 30 | 31 | @Override 32 | public void applyTo(Child child, TaggingFilterCriteria state) { 33 | if (child instanceof LeafChild) { 34 | ((LeafChild) child).addLeafHook(timeoutHook(timeout), HookContext.Precedence.ROOT); 35 | } 36 | } 37 | 38 | @Override 39 | public BlockConfigurable merge(BlockConfigurable other) { 40 | // my timeout supersedes any inherited timeout 41 | 42 | return this; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/ConfiguredBlock.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | 5 | /** 6 | * A block with configuration data applied to it. 7 | */ 8 | public class ConfiguredBlock implements Block { 9 | private final BlockConfiguration blockConfiguration; 10 | private final Block innerBlock; 11 | 12 | /** 13 | * Surround a {@link Block} with the {@code with} statement to add preconditions and metadata to it. 14 | * E.g. with(tags("foo"), () -> {}) 15 | * @param blockConfiguration the precondition object - see the factory methods in 16 | * {@link BlockConfiguration} 17 | * @param block the enclosed block 18 | * @return a PreconditionBlock to use 19 | */ 20 | public static ConfiguredBlock with(final BlockConfiguration blockConfiguration, 21 | final Block block) { 22 | 23 | BlockConfiguration existingBlockConfiguration = configurationFromBlock(block); 24 | BlockConfiguration mergedBlockConfiguration = 25 | BlockConfiguration.merge(existingBlockConfiguration, blockConfiguration); 26 | 27 | return new ConfiguredBlock(mergedBlockConfiguration, block); 28 | } 29 | 30 | /** 31 | * Construct a ConfiguredBlock to wrap block execution with configuration data. 32 | * @param innerBlock the block to wrap 33 | */ 34 | private ConfiguredBlock(final BlockConfiguration blockConfiguration, final Block innerBlock) { 35 | this.blockConfiguration = blockConfiguration; 36 | this.innerBlock = innerBlock; 37 | } 38 | 39 | /** 40 | * Get the configuration that applies to the block. 41 | * @return the configuration on the block 42 | */ 43 | BlockConfiguration getConfiguration() { 44 | return this.blockConfiguration; 45 | } 46 | 47 | @Override 48 | public void run() throws Throwable { 49 | this.innerBlock.run(); 50 | } 51 | 52 | /** 53 | * Provide any configuration data for this child's block or the default. 54 | * @param block the block which may have configuration data 55 | * @return a non null {@link BlockConfiguration} object to use 56 | */ 57 | public static BlockConfiguration configurationFromBlock(final Block block) { 58 | if (block instanceof ConfiguredBlock) { 59 | return ((ConfiguredBlock) block).getConfiguration(); 60 | } else { 61 | return BlockConfiguration.defaultConfiguration(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/ExcludeTags.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | 4 | import com.greghaskins.spectrum.internal.Suite; 5 | 6 | public class ExcludeTags implements SuiteConfigurable { 7 | 8 | private final String[] tags; 9 | 10 | public ExcludeTags(String[] tags) { 11 | this.tags = tags; 12 | } 13 | 14 | @Override 15 | public void applyTo(Suite suite) { 16 | suite.excludeTags(this.tags); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/IncludeTags.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | 4 | import com.greghaskins.spectrum.internal.Suite; 5 | 6 | public class IncludeTags implements SuiteConfigurable { 7 | 8 | private final String[] tags; 9 | 10 | public IncludeTags(String[] tags) { 11 | this.tags = tags; 12 | } 13 | 14 | @Override 15 | public void applyTo(Suite suite) { 16 | suite.includeTags(this.tags); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/SuiteConfigurable.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import com.greghaskins.spectrum.internal.Suite; 4 | 5 | public interface SuiteConfigurable { 6 | void applyTo(Suite suite); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/configuration/TaggingFilterCriteria.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.configuration; 2 | 3 | import com.greghaskins.spectrum.Configure; 4 | 5 | import java.util.Arrays; 6 | import java.util.Collection; 7 | import java.util.HashSet; 8 | import java.util.Optional; 9 | import java.util.Set; 10 | import java.util.stream.Stream; 11 | 12 | /** 13 | * Represents the state of tagging for Spectrum - what it presently means. 14 | */ 15 | public class TaggingFilterCriteria { 16 | 17 | private Set included = new HashSet<>(); 18 | private Set excluded = new HashSet<>(); 19 | private static final String TAGS_SEPARATOR = ","; 20 | 21 | public TaggingFilterCriteria() { 22 | include(fromSystemProperty(Configure.INCLUDE_TAGS_PROPERTY)); 23 | exclude(fromSystemProperty(Configure.EXCLUDE_TAGS_PROPERTY)); 24 | } 25 | 26 | public void include(String... tags) { 27 | include(Arrays.stream(tags)); 28 | } 29 | 30 | private void include(Stream tags) { 31 | this.included.clear(); 32 | tags.forEach(this.included::add); 33 | } 34 | 35 | public void exclude(String... tags) { 36 | exclude(Arrays.stream(tags)); 37 | } 38 | 39 | private void exclude(Stream tags) { 40 | this.excluded.clear(); 41 | tags.forEach(this.excluded::add); 42 | } 43 | 44 | @Override 45 | public TaggingFilterCriteria clone() { 46 | TaggingFilterCriteria copy = new TaggingFilterCriteria(); 47 | copy.include(this.included.stream()); 48 | copy.exclude(this.excluded.stream()); 49 | 50 | return copy; 51 | } 52 | 53 | boolean isAllowedToRun(Collection tags) { 54 | return !isExcluded(tags) && compliesWithRequired(tags); 55 | } 56 | 57 | private boolean isExcluded(Collection tags) { 58 | return tags.stream().anyMatch(this.excluded::contains); 59 | } 60 | 61 | private boolean compliesWithRequired(Collection tags) { 62 | return this.included.isEmpty() 63 | || tags.stream().anyMatch(this.included::contains); 64 | } 65 | 66 | private Stream fromSystemProperty(final String property) { 67 | return Optional.ofNullable(System.getProperty(property)) 68 | .map(string -> Arrays.stream(string.split(TaggingFilterCriteria.TAGS_SEPARATOR)) 69 | .filter(tag -> !tag.isEmpty())) 70 | .orElse(Stream.empty()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/AbstractSupplyingHook.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | import com.greghaskins.spectrum.Variable; 5 | import com.greghaskins.spectrum.internal.DeclarationState; 6 | import com.greghaskins.spectrum.internal.RunReporting; 7 | 8 | import org.junit.runner.Description; 9 | import org.junit.runner.notification.Failure; 10 | 11 | /** 12 | * A base class for {@link SupplyingHook hooks that supply a value}. 13 | * 14 | *

Override {@link #before} or {@link #after}. Return the singleton value from the before method. 15 | * You can use this to write any plugin which needs to make a value visible to the specs. 16 | * This is not the only way to achieve that - you can also build from {@link SupplyingHook} 17 | * but this captures the template for a complex hook. 18 | */ 19 | abstract class AbstractSupplyingHook implements SupplyingHook { 20 | 21 | private final Variable value = new Variable<>(); 22 | 23 | /** 24 | * Override this to supply behaviour for before the block is run. 25 | * 26 | * @return the value that the singleton will store to supply 27 | */ 28 | protected abstract T before(); 29 | 30 | /** 31 | * Override this to give a message for when the value from this hook gets used any time other than 32 | * while running a test. 33 | * 34 | * @return the IllegalStateException message to use 35 | */ 36 | protected abstract String getExceptionMessageIfUsedAtDeclarationTime(); 37 | 38 | /** 39 | * Override this to supply behaviour for after the block is run. 40 | */ 41 | protected void after() {} 42 | 43 | /** 44 | * Template method for a hook which supplies. 45 | * 46 | * @param description description - unused here 47 | * @param reporting reporting - unused here 48 | * @param block the inner block that will be run 49 | * @throws Throwable on error 50 | */ 51 | @Override 52 | public void accept(final Description description, final RunReporting reporting, 53 | final Block block) throws Throwable { 54 | try { 55 | this.value.set(before()); 56 | block.run(); 57 | } finally { 58 | try { 59 | after(); 60 | } finally { 61 | clear(); 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public T get() { 68 | assertSpectrumIsRunningTestsNotDeclaringThem(); 69 | 70 | return this.value.get(); 71 | } 72 | 73 | private void clear() { 74 | this.value.set(null); 75 | } 76 | 77 | /** 78 | * Will throw an exception if this method happens to be called while Spectrum is still defining 79 | * tests, rather than executing them. Useful to see if a hook is being accidentally used during 80 | * definition. 81 | */ 82 | private void assertSpectrumIsRunningTestsNotDeclaringThem() { 83 | if (DeclarationState.instance().getCurrentSuiteBeingDeclared() != null) { 84 | throw new IllegalStateException(getExceptionMessageIfUsedAtDeclarationTime()); 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/AfterHook.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | 5 | /** 6 | * A hook that will run after something. 7 | * @see BeforeHook 8 | */ 9 | public interface AfterHook { 10 | /** 11 | * Insert the block after the inner. 12 | * @param block the inner block 13 | * @return new {@link Hook} which runs the inner then the provided block 14 | */ 15 | static Hook after(final Block block) { 16 | return (description, notifier, inner) -> { 17 | try { 18 | inner.run(); 19 | } finally { 20 | block.run(); 21 | } 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/BeforeHook.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | 5 | /** 6 | * A hook that will run before something. 7 | * @see AfterHook 8 | */ 9 | public interface BeforeHook { 10 | /** 11 | * Insert the block before the inner. 12 | * @param block the inner block 13 | * @return new {@link Hook} which runs the provided block then the inner 14 | */ 15 | static Hook before(final Block block) { 16 | return (description, notifier, inner) -> { 17 | block.run(); 18 | inner.run(); 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/EagerLetHook.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import com.greghaskins.spectrum.ThrowingSupplier; 4 | 5 | /** 6 | * Implementation of an eager version of {@code let}. 7 | * 8 | *

Sematics are the same as with {@link LetHook}, except that all values are calculated at the 9 | * start of the test, rather than on an as-needed basis. 10 | */ 11 | public class EagerLetHook extends AbstractSupplyingHook { 12 | private final ThrowingSupplier supplier; 13 | 14 | public EagerLetHook(final ThrowingSupplier supplier) { 15 | this.supplier = supplier; 16 | } 17 | 18 | protected T before() { 19 | return supplier.get(); 20 | } 21 | 22 | protected String getExceptionMessageIfUsedAtDeclarationTime() { 23 | return "Cannot use the value from eagerLet() in a suite declaration. " 24 | + "It may only be used in the context of a running spec."; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/Hook.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | import com.greghaskins.spectrum.ThrowingConsumer; 5 | import com.greghaskins.spectrum.internal.RunReporting; 6 | 7 | import org.junit.runner.Description; 8 | import org.junit.runner.notification.Failure; 9 | 10 | /** 11 | * A hook allows you to inject functionality before and/or after a {@link Block}. 12 | * Just implement the {@link #accept(Description, RunReporting, Block)} method and 13 | * call {@link Block#run()} within your implementation. 14 | * If your hook is going to provide an object to the running test, then implement 15 | * {@link SupplyingHook} or subclass {@link AbstractSupplyingHook}. 16 | */ 17 | @FunctionalInterface 18 | public interface Hook { 19 | /** 20 | * Accept the block and execute it, hooking in any behaviour around it. 21 | * @param description description of where we are in the test 22 | * @param reporting the object to notify for failures 23 | * @param block the block to execute 24 | * @throws Throwable on error 25 | */ 26 | void accept(final Description description, final RunReporting reporting, 27 | final Block block) throws Throwable; 28 | 29 | /** 30 | * Override to return true if the inner block cannot report its own errors for some reason. 31 | * @return true to suppress wrapping inner block in self-reporting 32 | */ 33 | default boolean requiresUnreportedInnerBlock() { 34 | return false; 35 | } 36 | 37 | /** 38 | * Create a hook from a {@link ThrowingConsumer}. 39 | * @param consumer to turn into a hook 40 | * @return the hook 41 | */ 42 | static Hook from(ThrowingConsumer consumer) { 43 | return (description, notifier, block) -> consumer.accept(block); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/HookContext.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import static com.greghaskins.spectrum.internal.hooks.HookContext.AppliesTo.ATOMIC_ONLY; 4 | import static com.greghaskins.spectrum.internal.hooks.HookContext.AppliesTo.EACH_CHILD; 5 | import static com.greghaskins.spectrum.internal.hooks.HookContext.AppliesTo.ONCE; 6 | 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | /** 10 | * Container for a hook. Determines whether the hook runs just within the suite, or whether it 11 | * propagates down to test level. 12 | */ 13 | public class HookContext implements Comparable { 14 | private final Hook hook; 15 | private final AppliesTo appliesTo; 16 | private final int sequenceNumber; 17 | private final Precedence precedence; 18 | private final int hierarchyDepth; 19 | 20 | private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger(); 21 | 22 | /** 23 | * Where in the lifecycle the hook is applied. 24 | */ 25 | public enum AppliesTo { 26 | /** 27 | * Run around an atomic item. 28 | */ 29 | ATOMIC_ONLY, 30 | 31 | /** 32 | * Run once within the parent which declares it. 33 | */ 34 | ONCE, 35 | 36 | /** 37 | * Run for each immediate child of the parent which declares it. 38 | */ 39 | EACH_CHILD 40 | } 41 | 42 | 43 | /** 44 | * A precedence object to allow hooks to be used in the right order. 45 | * Note the internal integers help enforce the precedence but are not 46 | * for user consumption. 47 | */ 48 | public enum Precedence { 49 | /** 50 | * Guaranteed the first to be run. 51 | */ 52 | ROOT(0), 53 | 54 | /** 55 | * Aside from the root, this happens before anything else. 56 | */ 57 | OUTER(1), 58 | 59 | /** 60 | * Guaranteed tidy up code should be allowed to run no matter 61 | * what, once the test has started. 62 | */ 63 | GUARANTEED_CLEAN_UP_GLOBAL(2), 64 | 65 | /** 66 | * Set up code should run before the local context. 67 | */ 68 | SET_UP(3), 69 | 70 | /** 71 | * Guaranteed tidy up code should be allowed to run no matter 72 | * what, once the test has started. 73 | */ 74 | GUARANTEED_CLEAN_UP_LOCAL(4), 75 | 76 | /** 77 | * Local context - the order depends on declaration. 78 | */ 79 | LOCAL(5); 80 | 81 | private int ordering; 82 | 83 | /** 84 | * How the precedence is sequenced against others. 85 | * @return the order for sorting. 86 | */ 87 | int getOrdering() { 88 | return ordering; 89 | } 90 | 91 | Precedence(final int ordering) { 92 | this.ordering = ordering; 93 | } 94 | } 95 | 96 | /** 97 | * Construct a hook context. 98 | * @param hook the hook being wrapped 99 | * @param hierarchyDepth where in the hierarchy this was created 100 | * @param appliesTo where in the lifecycle is this hook applied? 101 | * @param precedence the importance of this within the lifecycle 102 | */ 103 | public HookContext(final Hook hook, final int hierarchyDepth, 104 | final AppliesTo appliesTo, final Precedence precedence) { 105 | this.hook = hook; 106 | this.appliesTo = appliesTo; 107 | this.sequenceNumber = SEQUENCE_GENERATOR.incrementAndGet(); 108 | this.precedence = precedence; 109 | this.hierarchyDepth = hierarchyDepth; 110 | } 111 | 112 | /** 113 | * Provides the hook within the context. 114 | * @return the hook 115 | */ 116 | public Hook getHook() { 117 | return hook; 118 | } 119 | 120 | /** 121 | * Does this hook apply only to atomic items. Atomic hooks will propagate 122 | * down to the most atomic level. 123 | * @return if this is for atomic items only 124 | */ 125 | public boolean isAtomicOnly() { 126 | return appliesTo.equals(ATOMIC_ONLY); 127 | } 128 | 129 | /** 130 | * Is this hook to be run once only. Such hooks are run at the level they 131 | * are declared and never again. 132 | * @return is this a one time hook? 133 | */ 134 | public boolean isOnce() { 135 | return appliesTo.equals(ONCE); 136 | } 137 | 138 | /** 139 | * Does this hook apply to all immediate children of the declared location. 140 | * @return if this is a hook to apply before each direct child 141 | */ 142 | public boolean isEachChild() { 143 | return appliesTo.equals(EACH_CHILD); 144 | } 145 | 146 | /** 147 | * In hook context terms, if this returns a positive number it means this item is 148 | * higher priority than other. 149 | * @param other to compare with 150 | * @return positive if this one is higher priority, negative if this one is 151 | * lower this can NEVER return 0 as no two contexts can be equal owing 152 | * to the auto incremented sequence number 153 | */ 154 | @Override 155 | public int compareTo(HookContext other) { 156 | // for us, lower means MORE IMPORTANT, so negate 157 | // the answer from a method which looks less surprising 158 | 159 | return -comparisonWith(other); 160 | } 161 | 162 | private int comparisonWith(HookContext other) { 163 | if (precedence != other.precedence) { 164 | return Integer.compare(precedence.getOrdering(), other.precedence.getOrdering()); 165 | } 166 | 167 | if (hierarchyDepth != other.hierarchyDepth) { 168 | return Integer.compare(hierarchyDepth, other.hierarchyDepth); 169 | } 170 | 171 | return Integer.compare(sequenceNumber, other.sequenceNumber); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/Hooks.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import static com.greghaskins.spectrum.internal.blocks.NotifyingBlock.wrapWithReporting; 4 | import static com.greghaskins.spectrum.internal.hooks.NonReportingHook.nonReportingHookFrom; 5 | 6 | import com.greghaskins.spectrum.Block; 7 | import com.greghaskins.spectrum.Variable; 8 | import com.greghaskins.spectrum.internal.RunReporting; 9 | import com.greghaskins.spectrum.internal.blocks.NotifyingBlock; 10 | 11 | import org.junit.runner.Description; 12 | import org.junit.runner.notification.Failure; 13 | 14 | import java.util.ArrayList; 15 | import java.util.function.Predicate; 16 | 17 | /** 18 | * Collection of hooks. It is a linked list, but provides some helpers for 19 | * passing hooks down a generation. 20 | */ 21 | public class Hooks extends ArrayList { 22 | private static final long serialVersionUID = 1L; 23 | 24 | public Hooks once() { 25 | return filtered(HookContext::isOnce); 26 | } 27 | 28 | public Hooks forNonAtomic() { 29 | return filtered(context -> !context.isOnce() && !context.isAtomicOnly()); 30 | } 31 | 32 | public Hooks forAtomic() { 33 | return filtered(HookContext::isAtomicOnly); 34 | } 35 | 36 | public Hooks forThisLevel() { 37 | return filtered(HookContext::isEachChild); 38 | } 39 | 40 | /** 41 | * Run the hooks on the right in the correct order AFTER these ones. 42 | * @param other to add to this 43 | * @return this for fluent use 44 | */ 45 | public Hooks plus(Hooks other) { 46 | addAll(other); 47 | 48 | return this; 49 | } 50 | 51 | /** 52 | * Return a hooks object where the hooks from this have been sorted into execution order. 53 | * @return new hooks sorted into the order for execution 54 | */ 55 | public Hooks sorted() { 56 | Hooks result = new Hooks(); 57 | result.addAll(this); 58 | result.sort(HookContext::compareTo); 59 | 60 | return result; 61 | } 62 | 63 | /** 64 | * Convert the hooks into a chain of responsibility and execute as 65 | * a consumer of the given block. 66 | * @param description test node being run 67 | * @param reporting test result notifier 68 | * @param block to execute 69 | */ 70 | public void runAround(final Description description, final RunReporting reporting, 71 | final Block block) { 72 | NotifyingBlock.run(description, reporting, 73 | () -> runAroundInternal(description, reporting, block)); 74 | } 75 | 76 | private void runAroundInternal(final Description description, 77 | final RunReporting reporting, 78 | final Block block) throws Throwable { 79 | Variable hooksRememberedToRunTheInner = new Variable<>(false); 80 | 81 | Hook chainOfResponsibility = createChainOfResponsibility(hooksRememberedToRunTheInner); 82 | executeChain(description, reporting, block, chainOfResponsibility); 83 | 84 | if (!hooksRememberedToRunTheInner.get()) { 85 | throw new RuntimeException("At least one of the test hooks did not run the test block."); 86 | } 87 | } 88 | 89 | private Hook createChainOfResponsibility(Variable hooksRememberedToRunTheInner) { 90 | Hook chainOfResponsibility = innerHook(hooksRememberedToRunTheInner); 91 | 92 | for (HookContext context : this) { 93 | chainOfResponsibility = wrap(chainOfResponsibility, context); 94 | } 95 | 96 | return chainOfResponsibility; 97 | } 98 | 99 | private void executeChain(final Description description, 100 | final RunReporting reporting, 101 | final Block block, final Hook chainOfResponsibility) throws Throwable { 102 | chainOfResponsibility.accept(description, reporting, block); 103 | } 104 | 105 | private Hook innerHook(final Variable hooksRememberedToRunTheInner) { 106 | return nonReportingHookFrom((description, reporting, block) -> { 107 | hooksRememberedToRunTheInner.set(true); 108 | block.run(); 109 | }); 110 | } 111 | 112 | private Hook wrap(final Hook inner, final HookContext outer) { 113 | return (description, reporting, block) -> outer.getHook().accept(description, reporting, 114 | conditionallyWrapWithReporting(inner, description, reporting, 115 | () -> inner.accept(description, reporting, block))); 116 | } 117 | 118 | private static Block conditionallyWrapWithReporting(final Hook forHook, final Description description, 119 | final RunReporting reporting, final Block innerBlock) { 120 | if (forHook.requiresUnreportedInnerBlock()) { 121 | return innerBlock; 122 | } 123 | 124 | return wrapWithReporting(description, reporting, innerBlock); 125 | } 126 | 127 | private Hooks filtered(Predicate predicate) { 128 | Hooks filtered = new Hooks(); 129 | stream().filter(predicate).forEach(filtered::add); 130 | 131 | return filtered; 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/LetHook.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import com.greghaskins.spectrum.Block; 4 | import com.greghaskins.spectrum.ThrowingSupplier; 5 | import com.greghaskins.spectrum.Variable; 6 | import com.greghaskins.spectrum.internal.DeclarationState; 7 | import com.greghaskins.spectrum.internal.RunReporting; 8 | 9 | import org.junit.runner.Description; 10 | import org.junit.runner.notification.Failure; 11 | 12 | /** 13 | * Implementation of {@code let} as a supplying hook. 14 | * 15 | *

Using {@code let} allows you to define shared values that can be used by multiple tests, 16 | * without having to worry about cleaning up the values between tests to prevent shared state in 17 | * one test from affecting the results of another. 18 | * 19 | *

Values are lazily initialized and then cached, so a value is not calculated until the first 20 | * time it is needed in a given test. Subsequent fetches of the value within the same test will 21 | * return the cached value. 22 | */ 23 | public class LetHook implements SupplyingHook { 24 | private final ThrowingSupplier supplier; 25 | private final Variable cachedValue = new Variable<>(); 26 | private boolean isCached; 27 | 28 | public LetHook(final ThrowingSupplier supplier) { 29 | this.supplier = supplier; 30 | this.isCached = false; 31 | } 32 | 33 | @Override 34 | public void accept(final Description description, 35 | final RunReporting reporting, final Block block) 36 | throws Throwable { 37 | try { 38 | block.run(); 39 | } finally { 40 | clear(); 41 | } 42 | } 43 | 44 | @Override 45 | public T get() { 46 | assertSpectrumIsRunningTestsNotDeclaringThem(); 47 | 48 | if (!this.isCached) { 49 | this.cachedValue.set(supplier.get()); 50 | 51 | this.isCached = true; 52 | } 53 | 54 | return this.cachedValue.get(); 55 | } 56 | 57 | protected String getExceptionMessageIfUsedAtDeclarationTime() { 58 | return "Cannot use the value from let() in a suite declaration. " 59 | + "It may only be used in the context of a running spec."; 60 | } 61 | 62 | private void clear() { 63 | this.isCached = false; 64 | this.cachedValue.set(null); 65 | } 66 | 67 | /** 68 | * Will throw an exception if this method happens to be called while Spectrum is still defining 69 | * tests, rather than executing them. Useful to see if a hook is being accidentally used during 70 | * definition. 71 | */ 72 | private void assertSpectrumIsRunningTestsNotDeclaringThem() { 73 | if (DeclarationState.instance().getCurrentSuiteBeingDeclared() != null) { 74 | throw new IllegalStateException(getExceptionMessageIfUsedAtDeclarationTime()); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/NonReportingHook.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | /** 4 | * A hook which requires that the block inside it is not self-reporting. 5 | */ 6 | public interface NonReportingHook extends Hook { 7 | /** 8 | * Factory method to create a hook which doesn't want its inner to be allowed to report on itself. 9 | * @param hook a hook object that can consume description, reporting and block to run for us to decorate 10 | * @return a non reportable hook 11 | */ 12 | static NonReportingHook nonReportingHookFrom(final Hook hook) { 13 | return hook::accept; 14 | } 15 | 16 | @Override 17 | default boolean requiresUnreportedInnerBlock() { 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/hooks/SupplyingHook.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.hooks; 2 | 3 | import java.util.function.Supplier; 4 | 5 | /** 6 | * A hook that supplies a value within a running test. The hook will consume 7 | * a block, which is to be run within the hook's setup/teardown. That block will access 8 | * the hook object as a supplier, which will provide an object of T. The T object should also 9 | * be destroyed by the hook's teardown. 10 | * @param the type of object the hook will supply within its execution scope. 11 | */ 12 | public interface SupplyingHook extends Hook, Supplier { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/junit/Rules.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.junit; 2 | 3 | import com.greghaskins.spectrum.internal.hooks.Hook; 4 | import com.greghaskins.spectrum.internal.hooks.HookContext; 5 | 6 | import java.util.function.Supplier; 7 | 8 | /** 9 | * How to hook JUnit rules into Spectrum. 10 | */ 11 | public interface Rules { 12 | 13 | @FunctionalInterface 14 | interface Target { 15 | void addHook(Hook hook, HookContext.AppliesTo appliesTo, HookContext.Precedence precedence); 16 | } 17 | 18 | /** 19 | * Apply JUnit rules by adding a hook to hook in the rules class. 20 | * This runs the rules for each atomic. 21 | * @param rulesClass type of object to create 22 | * @param target the insertion point to add JUnit rules as hooks 23 | * @param type of object that will be built 24 | * @return a supplier that provides access to the test object created 25 | */ 26 | static Supplier applyRules(Class rulesClass, Target target) { 27 | RuleContext context = new RuleContext<>(rulesClass); 28 | target.addHook(context.classHook(), HookContext.AppliesTo.ONCE, HookContext.Precedence.LOCAL); 29 | target.addHook(context.methodHook(), HookContext.AppliesTo.ATOMIC_ONLY, 30 | HookContext.Precedence.LOCAL); 31 | 32 | return context; 33 | } 34 | 35 | /** 36 | * Add a test object junit.rule set. 37 | * @param object the JUnit test object - this will never be recreated 38 | * @param target the insertion point to add JUnit rules as hooks 39 | * @param type of the test object 40 | */ 41 | static void applyRules(T object, Target target) { 42 | RuleContext context = new RuleContext<>(object); 43 | if (context.hasAnyJUnitAnnotations()) { 44 | target.addHook(context.classHook(), HookContext.AppliesTo.ONCE, 45 | HookContext.Precedence.LOCAL); 46 | target.addHook(context.methodHook(), HookContext.AppliesTo.ATOMIC_ONLY, 47 | HookContext.Precedence.LOCAL); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/junit/RunNotifierReporting.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.junit; 2 | 3 | import com.greghaskins.spectrum.internal.RunReporting; 4 | 5 | import org.junit.runner.Description; 6 | import org.junit.runner.notification.Failure; 7 | import org.junit.runner.notification.RunNotifier; 8 | 9 | import java.util.HashSet; 10 | import java.util.Objects; 11 | import java.util.Set; 12 | 13 | /** 14 | * Wraps the JUnit RunNotifier with the Spectrum run reporting interface. 15 | */ 16 | public class RunNotifierReporting implements RunReporting { 17 | private RunNotifier notifier; 18 | private Set reportedForFailure = new HashSet<>(); 19 | 20 | static class FailureWrapper { 21 | private Failure failure; 22 | 23 | public FailureWrapper(Failure failure) { 24 | this.failure = failure; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object other) { 29 | if (this == other) { 30 | return true; 31 | } 32 | if (other == null || getClass() != other.getClass()) { 33 | return false; 34 | } 35 | FailureWrapper that = (FailureWrapper) other; 36 | 37 | return Objects.equals(failure.getDescription(), that.failure.getDescription()) 38 | && Objects.equals(failure.getException(), that.failure.getException()); 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | return Objects.hash(failure.getDescription(), failure.getException()); 44 | } 45 | } 46 | 47 | public RunNotifierReporting(RunNotifier notifier) { 48 | this.notifier = notifier; 49 | } 50 | 51 | @Override 52 | public void fireTestIgnored(Description description) { 53 | notifier.fireTestIgnored(description); 54 | } 55 | 56 | @Override 57 | public void fireTestStarted(Description description) { 58 | notifier.fireTestStarted(description); 59 | } 60 | 61 | @Override 62 | public void fireTestFinished(Description description) { 63 | notifier.fireTestFinished(description); 64 | } 65 | 66 | @Override 67 | public void fireTestAssumptionFailed(Failure failure) { 68 | notifier.fireTestAssumptionFailed(failure); 69 | } 70 | 71 | @Override 72 | public void fireTestFailure(Failure failure) { 73 | FailureWrapper wrapper = new FailureWrapper(failure); 74 | if (!reportedForFailure.contains(wrapper)) { 75 | notifier.fireTestFailure(failure); 76 | reportedForFailure.add(wrapper); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/junit/StubJUnitFrameworkMethod.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.junit; 2 | 3 | import org.junit.runners.model.FrameworkMethod; 4 | 5 | /** 6 | * Provide an framework method for JUnit to use as a stub for its method rules. 7 | */ 8 | public interface StubJUnitFrameworkMethod { 9 | class Stub { 10 | public void method() {} 11 | } 12 | 13 | /** 14 | * Provide empty stub. 15 | * @return stub of nothing. 16 | */ 17 | static FrameworkMethod stubFrameworkMethod() { 18 | return stubFrameworkMethod(Stub.class, "method"); 19 | } 20 | 21 | /** 22 | * Arbitrary stub. 23 | * @param klazz class on which to find the method 24 | * @param methodName method to find 25 | * @return framework method wrapping the given method 26 | */ 27 | static FrameworkMethod stubFrameworkMethod(Class klazz, String methodName) { 28 | try { 29 | return new FrameworkMethod(klazz.getMethod(methodName)); 30 | } catch (NoSuchMethodException noSuchMethod) { 31 | throw new RuntimeException("Could not reach method", noSuchMethod); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/greghaskins/spectrum/internal/junit/TimeoutWrapper.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.junit; 2 | 3 | import static com.greghaskins.spectrum.internal.hooks.NonReportingHook.nonReportingHookFrom; 4 | import static com.greghaskins.spectrum.internal.junit.RuleContext.statementOf; 5 | 6 | import com.greghaskins.spectrum.internal.hooks.NonReportingHook; 7 | 8 | import org.junit.internal.runners.statements.FailOnTimeout; 9 | 10 | import java.time.Duration; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | * Wrap JUnit's timeout mechanism as a {@link NonReportingHook}. 15 | */ 16 | public interface TimeoutWrapper { 17 | /** 18 | * Convert the timeout into a {@link NonReportingHook} which executes 19 | * the inner inside a daemon thread, failing if it takes too long. 20 | * @param timeout duration of the timeout 21 | * @return hook which implements the timeout 22 | */ 23 | static NonReportingHook timeoutHook(Duration timeout) { 24 | return nonReportingHookFrom( 25 | (description, reporting, block) -> withAppliedTimeout(FailOnTimeout.builder(), timeout) 26 | .build(statementOf(block)) 27 | .evaluate()); 28 | } 29 | 30 | /** 31 | * Apply a timeout expressed as a duration to a builder of a {@link FailOnTimeout} object. 32 | * @param builder to modify 33 | * @param timeout duration of the timeout 34 | * @return the builder input - for fluent use. 35 | */ 36 | static FailOnTimeout.Builder withAppliedTimeout(FailOnTimeout.Builder builder, Duration timeout) { 37 | builder.withTimeout(timeout.toNanos(), TimeUnit.NANOSECONDS); 38 | 39 | return builder; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/greghaskins/spectrum/ParameterizedVariants.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.example; 4 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.scenarioOutline; 5 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.withExamples; 6 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 7 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 8 | import static org.hamcrest.core.Is.is; 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertNotNull; 11 | import static org.junit.Assert.assertThat; 12 | 13 | import org.junit.runner.RunWith; 14 | 15 | /** 16 | * Exercises all arg-variants of {@link com.greghaskins.spectrum.ParameterizedBlock}. 17 | */ 18 | @RunWith(Spectrum.class) 19 | public class ParameterizedVariants { 20 | { 21 | describe("parameterized definition block arguments", () -> { 22 | scenarioOutline("one arg", (arg) -> { 23 | it("can read the argument", () -> { 24 | assertNotNull(arg); 25 | }); 26 | }, withExamples(example("one"), example("two"))); 27 | 28 | scenarioOutline("two args", (intArg, stringArg) -> { 29 | it("arguments are correct", () -> { 30 | assertEquals(stringArg, Integer.toString(intArg)); 31 | }); 32 | }, withExamples(example(1, "1"), example(2, "2"))); 33 | 34 | scenarioOutline("three args", (intArg, stringArg, doubleArg) -> { 35 | it("int matches string", () -> { 36 | assertEquals(stringArg, Integer.toString(intArg)); 37 | }); 38 | it("int matches double", () -> { 39 | assertEquals((int) intArg, (int) doubleArg.doubleValue()); 40 | }); 41 | }, withExamples(example(1, "1", 1.0d), example(2, "2", 2.0d))); 42 | 43 | scenarioOutline("four args", (intArg, stringArg, doubleArg, booleanArg) -> { 44 | it("int matches string", () -> { 45 | assertEquals(stringArg, Integer.toString(intArg)); 46 | }); 47 | it("int matches double based on boolean", () -> { 48 | assertThat(intArg == (int) doubleArg.doubleValue(), is(booleanArg)); 49 | }); 50 | }, withExamples(example(1, "1", 1.0d, true), example(2, "2", 3.0d, false))); 51 | 52 | scenarioOutline("five args", (a1, a2, a3, a4, a5) -> { 53 | it("arguments add up", () -> { 54 | assertThat("" + a1 + a2 + a3 + a4, is(a5)); 55 | }); 56 | }, withExamples(example(1, "2", 3, "4", "1234"), example(2, "3", 4, "5", "2345"))); 57 | 58 | scenarioOutline("six args", (a1, a2, a3, a4, a5, a6) -> { 59 | it("arguments add up", () -> { 60 | assertThat("" + (a1 + a2) + a3 + a4 + a5, is(a6)); 61 | }); 62 | }, withExamples(example(0, 1, "2", 3, "4", "1234"), example(1, 1, "3", 4, "5", "2345"))); 63 | 64 | scenarioOutline("seven args", (a1, a2, a3, a4, a5, a6, a7) -> { 65 | it("arguments add up", () -> { 66 | assertThat(a1 + a2 + a3 + a4 + a5 + a6, is(a7)); 67 | }); 68 | }, withExamples(example("A", "B", "C", "D", "E", "F", "ABCDEF"))); 69 | 70 | scenarioOutline("eight args", (a1, a2, a3, a4, a5, a6, a7, a8) -> { 71 | it("arguments add up", () -> { 72 | assertThat(a1 == a2, is(a5)); 73 | assertThat(a3.equals(a4), is(a6)); 74 | assertThat(a1 + a2, is(a7)); 75 | assertThat(a3 + a4, is(a8)); 76 | }); 77 | }, withExamples(example(1, 2, "A", "B", false, false, 3, "AB"), 78 | example(1, 1, "A", "A", true, true, 2, "AA"))); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/greghaskins/spectrum/SpectrumHelper.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum; 2 | 3 | import org.junit.runner.Description; 4 | import org.junit.runner.JUnitCore; 5 | import org.junit.runner.Request; 6 | import org.junit.runner.Result; 7 | import org.junit.runner.Runner; 8 | import org.junit.runner.notification.RunListener; 9 | import org.junit.runner.notification.RunNotifier; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class SpectrumHelper { 15 | // Allows us to hide a test class from IDE/test discovery 16 | public static class NullRunner extends Runner { 17 | private Class clazz; 18 | 19 | public NullRunner(Class clazz) { 20 | this.clazz = clazz; 21 | } 22 | 23 | @Override 24 | public Description getDescription() { 25 | // empty suite 26 | 27 | return Description.createSuiteDescription(clazz); 28 | } 29 | 30 | @Override 31 | public void run(RunNotifier notifier) { 32 | // do nothing 33 | } 34 | } 35 | 36 | public static class RecordingListener extends RunListener { 37 | private List testsStarted = new ArrayList<>(); 38 | 39 | @Override 40 | public void testStarted(Description description) throws Exception { 41 | super.testStarted(description); 42 | testsStarted.add(description); 43 | } 44 | 45 | public List getTestsStarted() { 46 | return testsStarted; 47 | } 48 | } 49 | 50 | public static Result run(final Class specClass) throws Exception { 51 | return runWithJUnit(new Spectrum(specClass)); 52 | } 53 | 54 | public static Result run(final Block block) { 55 | return runWithJUnit( 56 | new Spectrum(Description.createSuiteDescription(block.getClass()), block)); 57 | } 58 | 59 | /** 60 | * Allows a listener to listen to a run. 61 | * @param specClass the class to execute via Spectrum 62 | * @param listener the listener to use 63 | * @param type of listener 64 | * @return the listener for fluent usage 65 | * @throws Exception on error 66 | */ 67 | public static T runWithListener(final Class specClass, 68 | final T listener) throws Exception { 69 | RunNotifier notifier = new RunNotifier(); 70 | notifier.addListener(listener); 71 | new Spectrum(specClass).run(notifier); 72 | 73 | return listener; 74 | } 75 | 76 | private static Result runWithJUnit(final Runner runner) { 77 | return new JUnitCore().run(Request.runner(runner)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/greghaskins/spectrum/internal/junit/RunNotifierReportingTest.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.internal.junit; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotEquals; 6 | 7 | import com.greghaskins.spectrum.internal.junit.RunNotifierReporting; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.Description; 11 | import org.junit.runner.notification.Failure; 12 | 13 | public class RunNotifierReportingTest { 14 | @Test 15 | public void equalsVariants() { 16 | // really to satisfy code coverage here 17 | RunNotifierReporting.FailureWrapper wrapper = new RunNotifierReporting.FailureWrapper(null); 18 | 19 | // all the edge cases for equals, right here 20 | assertEquals(wrapper, wrapper); 21 | assertNotEquals(wrapper, null); 22 | // here to force a branch in FailureWrapper.equals 23 | assertFalse(wrapper.equals(null)); 24 | assertFalse(wrapper.equals("Hello")); 25 | } 26 | 27 | @Test 28 | public void variationsOfFailureComparison() { 29 | Description desc1 = Description.createSuiteDescription("A"); 30 | Description desc2 = Description.createSuiteDescription("B"); 31 | Throwable exc1 = new RuntimeException("A"); 32 | Throwable exc2 = new RuntimeException("B"); 33 | 34 | assertEquals(new RunNotifierReporting.FailureWrapper(new Failure(desc1, exc1)), 35 | new RunNotifierReporting.FailureWrapper(new Failure(desc1, exc1))); 36 | 37 | assertNotEquals(new RunNotifierReporting.FailureWrapper(new Failure(desc1, exc1)), 38 | new RunNotifierReporting.FailureWrapper(new Failure(desc1, exc2))); 39 | 40 | assertNotEquals(new RunNotifierReporting.FailureWrapper(new Failure(desc1, exc1)), 41 | new RunNotifierReporting.FailureWrapper(new Failure(desc2, exc1))); 42 | 43 | assertNotEquals(new RunNotifierReporting.FailureWrapper(new Failure(desc1, exc1)), 44 | new RunNotifierReporting.FailureWrapper(new Failure(desc2, exc2))); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/greghaskins/spectrum/model/HookContextTest.java: -------------------------------------------------------------------------------- 1 | package com.greghaskins.spectrum.model; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.greaterThan; 7 | import static org.hamcrest.core.Is.is; 8 | import static org.hamcrest.core.IsNot.not; 9 | 10 | import com.greghaskins.spectrum.Spectrum; 11 | import com.greghaskins.spectrum.internal.hooks.Hook; 12 | import com.greghaskins.spectrum.internal.hooks.HookContext; 13 | 14 | import org.junit.runner.RunWith; 15 | 16 | /** 17 | * Tests for the {@link HookContext} class. Its comparison 18 | * algorithm is important enough to need testing. 19 | */ 20 | @RunWith(Spectrum.class) 21 | public class HookContextTest { 22 | { 23 | describe("Hook context", () -> { 24 | it("can never consider two hook contexts equal", () -> { 25 | HookContext one = new HookContext(emptyHook(), 26 | 0, HookContext.AppliesTo.ATOMIC_ONLY, HookContext.Precedence.ROOT); 27 | HookContext two = new HookContext(emptyHook(), 28 | 0, HookContext.AppliesTo.ATOMIC_ONLY, HookContext.Precedence.ROOT); 29 | assertThat(one.compareTo(two), not(is(0))); 30 | }); 31 | 32 | it("considers the later created one to be less important", () -> { 33 | HookContext one = new HookContext(emptyHook(), 34 | 0, HookContext.AppliesTo.ATOMIC_ONLY, HookContext.Precedence.ROOT); 35 | HookContext two = new HookContext(emptyHook(), 36 | 0, HookContext.AppliesTo.ATOMIC_ONLY, HookContext.Precedence.ROOT); 37 | assertFirstIsMoreImportantThanSecond(one, two); 38 | }); 39 | 40 | it("considers a higher level precedence to be more important" 41 | + " than a sequence number", () -> { 42 | HookContext one = new HookContext(emptyHook(), 43 | 0, HookContext.AppliesTo.ATOMIC_ONLY, HookContext.Precedence.OUTER); 44 | HookContext two = new HookContext(emptyHook(), 45 | 0, HookContext.AppliesTo.ATOMIC_ONLY, HookContext.Precedence.ROOT); 46 | assertFirstIsMoreImportantThanSecond(two, one); 47 | }); 48 | 49 | it("considers a lower depth to be more important", () -> { 50 | HookContext one = new HookContext(emptyHook(), 51 | 1, HookContext.AppliesTo.ATOMIC_ONLY, HookContext.Precedence.ROOT); 52 | HookContext two = new HookContext(emptyHook(), 53 | 0, HookContext.AppliesTo.ATOMIC_ONLY, HookContext.Precedence.ROOT); 54 | assertFirstIsMoreImportantThanSecond(two, one); 55 | }); 56 | }); 57 | 58 | } 59 | 60 | private Hook emptyHook() { 61 | return (description, notifier, block) -> { 62 | }; 63 | } 64 | 65 | private static void assertFirstIsMoreImportantThanSecond(HookContext first, HookContext second) { 66 | assertThat(first.compareTo(second), greaterThan(0)); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/bdd/annotation/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.bdd.annotation; 2 | 3 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.and; 4 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.feature; 5 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.given; 6 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.scenario; 7 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.then; 8 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.when; 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | import static org.hamcrest.Matchers.contains; 11 | import static org.hamcrest.core.Is.is; 12 | 13 | import com.greghaskins.spectrum.Spectrum; 14 | 15 | import org.junit.Assert; 16 | import org.junit.Before; 17 | import org.junit.Test; 18 | import org.junit.runner.Description; 19 | 20 | import java.util.stream.Collectors; 21 | 22 | public class WhenDescribingTheSpec { 23 | private Description mainDescription; 24 | 25 | @Before 26 | public void before() throws Exception { 27 | final Description rootDescription = 28 | new Spectrum(getBddExampleSpec()).getDescription(); 29 | this.mainDescription = rootDescription.getChildren().get(0); 30 | } 31 | 32 | @Test 33 | public void theTopLevelIsAFeature() throws Exception { 34 | assertThat(this.mainDescription.getDisplayName(), 35 | is("Feature: BDD semantics")); 36 | } 37 | 38 | @Test 39 | public void theNextLevelIsAScenario() throws Exception { 40 | assertThat(this.mainDescription.getChildren().get(0).getDisplayName(), 41 | is("Scenario: a named scenario with")); 42 | } 43 | 44 | @Test 45 | public void theScenarioHasGivenWhenThen() throws Exception { 46 | assertThat(this.mainDescription.getChildren().get(0).getChildren() 47 | .stream().map(Description::getDisplayName) 48 | .collect(Collectors.toList()), 49 | contains("Given some sort of given(Scenario: a named scenario with)", 50 | "When some sort of when(Scenario: a named scenario with)", 51 | "Then some sort of outcome(Scenario: a named scenario with)", 52 | "And an and on the end(Scenario: a named scenario with)")); 53 | } 54 | 55 | private static Class getBddExampleSpec() { 56 | class Spec { 57 | { 58 | feature("BDD semantics", () -> { 59 | 60 | scenario("a named scenario with", () -> { 61 | 62 | given("some sort of given", () -> { 63 | Assert.assertTrue(true); 64 | }); 65 | 66 | when("some sort of when", () -> { 67 | Assert.assertTrue(true); 68 | }); 69 | 70 | then("some sort of outcome", () -> { 71 | Assert.assertTrue(true); 72 | }); 73 | 74 | and("an and on the end", () -> { 75 | Assert.assertTrue(true); 76 | }); 77 | 78 | }); 79 | }); 80 | } 81 | } 82 | 83 | return Spec.class; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/bdd/annotation/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.bdd.annotation; 2 | 3 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.feature; 4 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.given; 5 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.scenario; 6 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.then; 7 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.when; 8 | import static org.junit.Assert.assertFalse; 9 | import static org.junit.Assume.assumeTrue; 10 | 11 | import com.greghaskins.spectrum.SpectrumHelper; 12 | 13 | import org.junit.Assert; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | public class WhenRunningTheSpec { 18 | private static boolean thenRan = false; 19 | 20 | @Before 21 | public void before() { 22 | thenRan = false; 23 | } 24 | 25 | @Test 26 | public void bddStepFailureStopsTheSpec() throws Exception { 27 | SpectrumHelper.run(getBddExampleWhichFailsSpec()); 28 | assertFalse(thenRan); 29 | } 30 | 31 | @Test 32 | public void bddAssumptionFailureStopsTheSpec() throws Exception { 33 | SpectrumHelper.run(getBddExampleWithAssumptionFailure()); 34 | assertFalse(thenRan); 35 | } 36 | 37 | private static Class getBddExampleWhichFailsSpec() { 38 | class Spec { 39 | { 40 | feature("BDD steps stop at failure", () -> { 41 | 42 | scenario("failing at when", () -> { 43 | 44 | given("a passing given", () -> { 45 | Assert.assertTrue(true); 46 | }); 47 | 48 | when("the when fails", () -> { 49 | Assert.assertTrue(false); 50 | }); 51 | 52 | then("the then can't do its thing", () -> { 53 | thenRan = true; 54 | }); 55 | 56 | }); 57 | }); 58 | } 59 | } 60 | 61 | return Spec.class; 62 | } 63 | 64 | private static Class getBddExampleWithAssumptionFailure() { 65 | class Spec { 66 | { 67 | feature("BDD steps stop at failure", () -> { 68 | 69 | scenario("failing at when", () -> { 70 | 71 | given("an assumption failure in step one", () -> { 72 | assumeTrue(false); 73 | }); 74 | 75 | when("the when can't do its thing", () -> { 76 | thenRan = true; 77 | }); 78 | 79 | then("the then can't do its thing", () -> { 80 | thenRan = true; 81 | }); 82 | 83 | }); 84 | }); 85 | } 86 | } 87 | 88 | return Spec.class; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/constructor/parameters/Fixture.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.constructor.parameters; 2 | 3 | class Fixture { 4 | 5 | public static Class getSpecThatRequiresAConstructorParameter() { 6 | class Spec { 7 | @SuppressWarnings("unused") 8 | public Spec(final String something) {} 9 | } 10 | 11 | return Spec.class; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/constructor/parameters/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.constructor.parameters; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasSize; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.Spectrum; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Description; 12 | 13 | public class WhenDescribingTheSpec { 14 | 15 | private Description description; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.description = 20 | new Spectrum(Fixture.getSpecThatRequiresAConstructorParameter()).getDescription(); 21 | } 22 | 23 | @Test 24 | public void thereIsOneChildOfTheExplodingContext() throws Exception { 25 | assertThat(getDescriptionForExplodingContext().getChildren(), hasSize(1)); 26 | } 27 | 28 | @Test 29 | public void itIsClearThatAnErrorWasEncountered() throws Exception { 30 | assertThat(getDescriptionForError().getMethodName(), is("encountered an error")); 31 | } 32 | 33 | @Test 34 | public void itIsClearWhichDescribeBlockHadTheError() throws Exception { 35 | assertThat(getDescriptionForError().getClassName(), 36 | is(Fixture.getSpecThatRequiresAConstructorParameter().getName())); 37 | } 38 | 39 | private Description getDescriptionForError() { 40 | return getDescriptionForExplodingContext().getChildren().get(0); 41 | } 42 | 43 | private Description getDescriptionForExplodingContext() { 44 | return this.description; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/constructor/parameters/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.constructor.parameters; 2 | 3 | import static matchers.IsFailure.failure; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.SpectrumHelper; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Result; 12 | 13 | public class WhenRunningTheSpec { 14 | 15 | private Result result; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.result = SpectrumHelper.run(Fixture.getSpecThatRequiresAConstructorParameter()); 20 | } 21 | 22 | @Test 23 | public void thereIsOneFailure() throws Exception { 24 | assertThat(this.result.getFailureCount(), is(1)); 25 | } 26 | 27 | @Test 28 | public void theFailureExplainsWhatHappened() throws Exception { 29 | assertThat(this.result.getFailures().get(0), 30 | is(failure("encountered an error", RuntimeException.class, 31 | Fixture.getSpecThatRequiresAConstructorParameter().getName()))); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/aftereach/block/Fixture.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.aftereach.block; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.afterEach; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 6 | 7 | 8 | class Fixture { 9 | 10 | public static Class getSpecThatThrowsAnExceptionInAfterEachBlock() { 11 | class Spec { 12 | { 13 | describe("an exploding afterEach", () -> { 14 | 15 | afterEach(() -> { 16 | throw new SomeException("kaboom"); 17 | }); 18 | 19 | it("a passing test", () -> { 20 | 21 | }); 22 | }); 23 | } 24 | } 25 | 26 | return Spec.class; 27 | } 28 | 29 | public static class SomeException extends Exception { 30 | private static final long serialVersionUID = 1L; 31 | 32 | public SomeException(final String message) { 33 | super(message); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/aftereach/block/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.aftereach.block; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.is; 5 | 6 | import com.greghaskins.spectrum.Spectrum; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.Description; 11 | 12 | public class WhenDescribingTheSpec { 13 | 14 | private Description description; 15 | 16 | @Before 17 | public void before() throws Exception { 18 | this.description = 19 | new Spectrum(Fixture.getSpecThatThrowsAnExceptionInAfterEachBlock()).getDescription(); 20 | } 21 | 22 | @Test 23 | public void itIsClearThatAnErrorWasEncountered() throws Exception { 24 | assertThat(getDescriptionForError().getMethodName(), is("a passing test")); 25 | } 26 | 27 | @Test 28 | public void itIsClearWhichBeforeEachBlockHadTheError() throws Exception { 29 | assertThat(getDescriptionForError().getClassName(), is("an exploding afterEach")); 30 | } 31 | 32 | private Description getDescriptionForError() { 33 | return getFirstContext().getChildren().get(0); 34 | } 35 | 36 | private Description getFirstContext() { 37 | return this.description.getChildren().get(0); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/aftereach/block/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.aftereach.block; 2 | 3 | import static matchers.IsFailure.failure; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.SpectrumHelper; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Result; 12 | 13 | public class WhenRunningTheSpec { 14 | 15 | private Result result; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.result = SpectrumHelper.run(Fixture.getSpecThatThrowsAnExceptionInAfterEachBlock()); 20 | } 21 | 22 | @Test 23 | public void thereIsOneFailureForEachAffectedTest() throws Exception { 24 | assertThat(this.result.getFailureCount(), is(1)); 25 | } 26 | 27 | @Test 28 | public void theFailureExplainsWhatHappened() throws Exception { 29 | assertThat(this.result.getFailures().get(0), 30 | is(failure("a passing test", Fixture.SomeException.class, 31 | "kaboom"))); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/beforeeach/block/and/aftereach/block/Fixture.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.beforeeach.block.and.aftereach.block; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.afterEach; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.beforeEach; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 6 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 7 | 8 | 9 | class Fixture { 10 | 11 | public static Class getSpecThatThrowsAnExceptionInBeforeEachAndAfterEachBlocks() { 12 | class Spec { 13 | { 14 | describe("an exploding beforeEach", () -> { 15 | 16 | beforeEach(() -> { 17 | throw new SomeException("beforeEach went kaboom"); 18 | }); 19 | 20 | afterEach(() -> { 21 | throw new SomeException("afterEach went poof"); 22 | }); 23 | 24 | it("a failing test", () -> { 25 | throw new Exception(); 26 | }); 27 | 28 | it("another failing test", () -> { 29 | throw new Exception(); 30 | }); 31 | }); 32 | } 33 | } 34 | 35 | return Spec.class; 36 | } 37 | 38 | public static class SomeException extends RuntimeException { 39 | private static final long serialVersionUID = 1L; 40 | 41 | public SomeException(final String message) { 42 | super(message); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/beforeeach/block/and/aftereach/block/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.beforeeach.block.and.aftereach.block; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasSize; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.Spectrum; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Description; 12 | 13 | public class WhenDescribingTheSpec { 14 | 15 | private Description description; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.description = 20 | new Spectrum(Fixture.getSpecThatThrowsAnExceptionInBeforeEachAndAfterEachBlocks()) 21 | .getDescription(); 22 | } 23 | 24 | @Test 25 | public void thereAreTwoTests() throws Exception { 26 | assertThat(getFirstContext().getChildren(), hasSize(2)); 27 | } 28 | 29 | @Test 30 | public void itIsClearThatAnErrorWasEncountered() throws Exception { 31 | assertThat(getDescriptionForError().getMethodName(), is("a failing test")); 32 | } 33 | 34 | @Test 35 | public void itIsClearWhichBeforeEachBlockHadTheError() throws Exception { 36 | assertThat(getDescriptionForError().getClassName(), is("an exploding beforeEach")); 37 | } 38 | 39 | private Description getDescriptionForError() { 40 | return getFirstContext().getChildren().get(0); 41 | } 42 | 43 | private Description getFirstContext() { 44 | return this.description.getChildren().get(0); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/beforeeach/block/and/aftereach/block/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.beforeeach.block.and.aftereach.block; 2 | 3 | import static matchers.IsFailure.failure; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.SpectrumHelper; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Result; 12 | 13 | public class WhenRunningTheSpec { 14 | 15 | private Result result; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.result = 20 | SpectrumHelper.run(Fixture.getSpecThatThrowsAnExceptionInBeforeEachAndAfterEachBlocks()); 21 | } 22 | 23 | @Test 24 | public void thereAreTwoFailuresForEachAffectedTest() throws Exception { 25 | assertThat(this.result.getFailureCount(), is(4)); 26 | } 27 | 28 | @Test 29 | public void theFailuresExplainWhatHappened() throws Exception { 30 | assertThat(this.result.getFailures().get(0), 31 | is(failure("a failing test", Fixture.SomeException.class, 32 | "beforeEach went kaboom"))); 33 | assertThat(this.result.getFailures().get(1), 34 | is(failure("a failing test", Fixture.SomeException.class, 35 | "afterEach went poof"))); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/constructor/Fixture.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.constructor; 2 | 3 | class Fixture { 4 | 5 | public static Class getSpecThatThrowsAnExceptionInConstructor() { 6 | class Spec { 7 | { 8 | if (true) { 9 | throw new SomeException("kaboom"); 10 | } 11 | } 12 | } 13 | 14 | return Spec.class; 15 | } 16 | 17 | public static class SomeException extends RuntimeException { 18 | private static final long serialVersionUID = 1L; 19 | 20 | public SomeException(final String message) { 21 | super(message); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/constructor/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.constructor; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasSize; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.Spectrum; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Description; 12 | 13 | public class WhenDescribingTheSpec { 14 | 15 | private Description description; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.description = 20 | new Spectrum(Fixture.getSpecThatThrowsAnExceptionInConstructor()).getDescription(); 21 | } 22 | 23 | @Test 24 | public void thereIsOneChildOfTheExplodingContext() throws Exception { 25 | assertThat(getDescriptionForExplodingContext().getChildren(), hasSize(1)); 26 | } 27 | 28 | @Test 29 | public void itIsClearThatAnErrorWasEncountered() throws Exception { 30 | assertThat(getDescriptionForError().getMethodName(), is("encountered an error")); 31 | } 32 | 33 | @Test 34 | public void itIsClearWhichDescribeBlockHadTheError() throws Exception { 35 | assertThat(getDescriptionForError().getClassName(), 36 | is(Fixture.getSpecThatThrowsAnExceptionInConstructor().getName())); 37 | } 38 | 39 | private Description getDescriptionForError() { 40 | return getDescriptionForExplodingContext().getChildren().get(0); 41 | } 42 | 43 | private Description getDescriptionForExplodingContext() { 44 | return this.description; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/constructor/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.constructor; 2 | 3 | import static matchers.IsFailure.failure; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.SpectrumHelper; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Result; 12 | 13 | public class WhenRunningTheSpec { 14 | 15 | private Result result; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.result = SpectrumHelper.run(Fixture.getSpecThatThrowsAnExceptionInConstructor()); 20 | } 21 | 22 | @Test 23 | public void thereIsOneFailure() throws Exception { 24 | assertThat(this.result.getFailureCount(), is(1)); 25 | } 26 | 27 | @Test 28 | public void theFailureExplainsWhatHappened() throws Exception { 29 | assertThat(this.result.getFailures().get(0), 30 | is(failure("encountered an error", Fixture.SomeException.class, "kaboom"))); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/describe/block/Fixture.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.describe.block; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 5 | 6 | 7 | class Fixture { 8 | 9 | public static Class getSpecThatThrowsAnExceptionInDescribeBlock() { 10 | @SuppressWarnings("unused") 11 | class Spec { 12 | { 13 | describe("an exploding context", () -> { 14 | 15 | it("should not run", () -> { 16 | throw new Exception(); 17 | }); 18 | 19 | if (true) { 20 | throw new SomeException("kaboom"); 21 | } 22 | 23 | it("also should not run", () -> { 24 | throw new Exception(); 25 | }); 26 | }); 27 | } 28 | } 29 | 30 | return Spec.class; 31 | } 32 | 33 | public static class SomeException extends Exception { 34 | private static final long serialVersionUID = 1L; 35 | 36 | public SomeException(final String message) { 37 | super(message); 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/describe/block/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.describe.block; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasSize; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.Spectrum; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Description; 12 | 13 | public class WhenDescribingTheSpec { 14 | 15 | private Description description; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.description = 20 | new Spectrum(Fixture.getSpecThatThrowsAnExceptionInDescribeBlock()).getDescription(); 21 | } 22 | 23 | @Test 24 | public void thereIsOneChildOfTheExplodingContext() throws Exception { 25 | assertThat(getDescriptionForExplodingContext().getChildren(), hasSize(1)); 26 | } 27 | 28 | @Test 29 | public void itIsClearThatAnErrorWasEncountered() throws Exception { 30 | assertThat(getDescriptionForError().getMethodName(), is("encountered an error")); 31 | } 32 | 33 | @Test 34 | public void itIsClearWhichDescribeBlockHadTheError() throws Exception { 35 | assertThat(getDescriptionForError().getClassName(), is("an exploding context")); 36 | } 37 | 38 | private Description getDescriptionForError() { 39 | return getDescriptionForExplodingContext().getChildren().get(0); 40 | } 41 | 42 | private Description getDescriptionForExplodingContext() { 43 | return this.description.getChildren().get(0); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/exception/in/describe/block/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.exception.in.describe.block; 2 | 3 | import static matchers.IsFailure.failure; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.SpectrumHelper; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Result; 12 | 13 | public class WhenRunningTheSpec { 14 | 15 | private Result result; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.result = SpectrumHelper.run(Fixture.getSpecThatThrowsAnExceptionInDescribeBlock()); 20 | } 21 | 22 | @Test 23 | public void thereIsOneAndOnlyOneFailure() throws Exception { 24 | assertThat(this.result.getFailureCount(), is(1)); 25 | } 26 | 27 | @Test 28 | public void theFailureExplainsWhatHappened() throws Exception { 29 | assertThat(this.result.getFailures().get(0), 30 | is(failure("encountered an error", Fixture.SomeException.class, "kaboom"))); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/naming/problems/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.naming.problems; 2 | 3 | import static com.greghaskins.spectrum.SpectrumHelper.runWithListener; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 6 | import static org.hamcrest.core.Is.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | import com.greghaskins.spectrum.SpectrumHelper; 10 | 11 | import org.junit.Test; 12 | import org.junit.runner.Description; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Name filtering tests, to demonstrate how Spectrum will handle naming problems 18 | * of duplication and bad characters. 19 | */ 20 | public class WhenRunningTheSpec { 21 | @Test 22 | public void theNamesWillBeFilteredForDuplication() throws Exception { 23 | List names = 24 | runWithListener(specWithDuplicates(), new SpectrumHelper.RecordingListener()) 25 | .getTestsStarted(); 26 | 27 | assertThat(names.get(0).getDisplayName(), is("is awesome(My suite)")); 28 | assertThat(names.get(1).getDisplayName(), is("is awesome(My suite_1)")); 29 | assertThat(names.get(2).getDisplayName(), is("is awesome(My other suite)")); 30 | assertThat(names.get(3).getDisplayName(), is("is awesome_1(My other suite)")); 31 | assertThat(names.get(4).getDisplayName(), is("is awesome_2(My other suite)")); 32 | } 33 | 34 | @Test 35 | public void theNamesWillBeFilteredForBadCharacters() throws Exception { 36 | List names = 37 | runWithListener(specWithBadCharacters(), new SpectrumHelper.RecordingListener()) 38 | .getTestsStarted(); 39 | 40 | assertThat(names.get(0).getDisplayName(), is("is awesome [totally](My suite [awesome])")); 41 | } 42 | 43 | private static Class specWithDuplicates() { 44 | class SpecWithDuplicates { 45 | { 46 | describe("My suite", () -> { 47 | it("is awesome", () -> { 48 | }); 49 | }); 50 | 51 | describe("My suite", () -> { 52 | it("is awesome", () -> { 53 | }); 54 | }); 55 | 56 | describe("My other suite", () -> { 57 | it("is awesome", () -> { 58 | }); 59 | it("is awesome", () -> { 60 | }); 61 | it("is awesome", () -> { 62 | }); 63 | }); 64 | 65 | } 66 | } 67 | 68 | return SpecWithDuplicates.class; 69 | } 70 | 71 | private static Class specWithBadCharacters() { 72 | class SpecWithWithBadCharacters { 73 | { 74 | describe("My suite (awesome)", () -> { 75 | it("is awesome (totally)", () -> { 76 | }); 77 | }); 78 | 79 | } 80 | } 81 | 82 | return SpecWithWithBadCharacters.class; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/nested/describe/blocks/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.nested.describe.blocks; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.contains; 7 | import static org.hamcrest.Matchers.hasSize; 8 | 9 | import com.greghaskins.spectrum.Spectrum; 10 | 11 | import org.junit.Assert; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.Description; 15 | 16 | public class WhenDescribingTheSpec { 17 | 18 | private Description mainDescription; 19 | 20 | @Before 21 | public void before() throws Exception { 22 | final Description rootDescription = 23 | new Spectrum(getSpecWithNestedDescribeBlocks()).getDescription(); 24 | this.mainDescription = rootDescription.getChildren().get(0); 25 | } 26 | 27 | @Test 28 | public void theMainDescriptionHasTwoContextsAsChildren() throws Exception { 29 | assertThat(this.mainDescription.getChildren(), 30 | contains(Description.createSuiteDescription("with a first child context"), 31 | Description.createSuiteDescription("with a second child context"))); 32 | } 33 | 34 | @Test 35 | public void theFirstSubContextHasThreeTests() throws Exception { 36 | assertThat(this.mainDescription.getChildren().get(0).getChildren(), hasSize(3)); 37 | } 38 | 39 | @Test 40 | public void theSecondSubContextHasOneTest() throws Exception { 41 | assertThat(this.mainDescription.getChildren().get(1).getChildren(), hasSize(1)); 42 | } 43 | 44 | private static Class getSpecWithNestedDescribeBlocks() { 45 | class Spec { 46 | { 47 | 48 | describe("the main context", () -> { 49 | 50 | describe("with a first child context", () -> { 51 | 52 | it("has a test", () -> { 53 | Assert.assertTrue(true); 54 | }); 55 | 56 | it("has another test", () -> { 57 | Assert.assertTrue(true); 58 | }); 59 | 60 | it("has a third test", () -> { 61 | Assert.assertTrue(true); 62 | }); 63 | 64 | }); 65 | 66 | describe("with a second child context", () -> { 67 | 68 | it("does something", () -> { 69 | Assert.assertTrue(true); 70 | }); 71 | 72 | }); 73 | 74 | }); 75 | } 76 | } 77 | 78 | return Spec.class; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/one/passing/test/Fixture.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.one.passing.test; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 5 | 6 | 7 | class Fixture { 8 | 9 | public static Class getSpecWithOnePassingTest() { 10 | class Spec { 11 | { 12 | describe("a spec with one passing test", () -> { 13 | 14 | it("should pass", () -> { 15 | 16 | }); 17 | 18 | }); 19 | } 20 | } 21 | 22 | return Spec.class; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/one/passing/test/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.one.passing.test; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasSize; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.Spectrum; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Description; 12 | 13 | public class WhenDescribingTheSpec { 14 | 15 | private Description description; 16 | 17 | @Before 18 | public void before() throws Exception { 19 | this.description = new Spectrum(Fixture.getSpecWithOnePassingTest()).getDescription(); 20 | } 21 | 22 | @Test 23 | public void theRootSuiteIsTheTestClass() throws Exception { 24 | assertThat(this.description.getDisplayName(), 25 | is(Fixture.getSpecWithOnePassingTest().getName())); 26 | } 27 | 28 | @Test 29 | public void thereIsOneChildSuite() throws Exception { 30 | assertThat(this.description.getChildren(), hasSize(1)); 31 | } 32 | 33 | @Test 34 | public void theSuiteDescriptionIsCorrect() throws Exception { 35 | assertThat(getFirstChildSuite().getDisplayName(), is("a spec with one passing test")); 36 | } 37 | 38 | @Test 39 | public void thereIsOneChildTest() throws Exception { 40 | assertThat(getFirstChildSuite().getChildren(), hasSize(1)); 41 | } 42 | 43 | @Test 44 | public void theTestNameIsCorrect() throws Exception { 45 | assertThat(getFirstChildSuite().getChildren().get(0).getMethodName(), is("should pass")); 46 | } 47 | 48 | private Description getFirstChildSuite() { 49 | return this.description.getChildren().get(0); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/one/passing/test/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.one.passing.test; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.is; 5 | 6 | import com.greghaskins.spectrum.SpectrumHelper; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.Result; 11 | 12 | public class WhenRunningTheSpec { 13 | 14 | private Result result; 15 | 16 | @Before 17 | public void before() throws Exception { 18 | this.result = SpectrumHelper.run(Fixture.getSpecWithOnePassingTest()); 19 | } 20 | 21 | @Test 22 | public void theRunCountIsOne() throws Exception { 23 | assertThat(this.result.getRunCount(), is(1)); 24 | } 25 | 26 | @Test 27 | public void theRunIsSuccessful() throws Exception { 28 | assertThat(this.result.wasSuccessful(), is(true)); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/passing/and/failing/tests/Fixture.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.passing.and.failing.tests; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 5 | 6 | import org.junit.Assert; 7 | 8 | class Fixture { 9 | 10 | public static Class getSpecWithPassingAndFailingTests() { 11 | class Spec { 12 | { 13 | describe("a spec with three passing and two failing tests", () -> { 14 | 15 | it("fails test 1", () -> { 16 | Assert.fail("failure message one"); 17 | }); 18 | 19 | it("passes test 2", () -> { 20 | Assert.assertTrue(true); 21 | }); 22 | 23 | it("passes test 3", () -> { 24 | Assert.assertTrue(true); 25 | }); 26 | 27 | it("fails test 4", () -> { 28 | throw new Exception("failure message four"); 29 | }); 30 | 31 | it("passes test 5", () -> { 32 | Assert.assertTrue(true); 33 | }); 34 | 35 | }); 36 | } 37 | } 38 | 39 | return Spec.class; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/passing/and/failing/tests/WhenDescribingTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.passing.and.failing.tests; 2 | 3 | import static org.hamcrest.Matchers.hasSize; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import com.greghaskins.spectrum.Spectrum; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Description; 12 | 13 | import java.util.ArrayList; 14 | 15 | public class WhenDescribingTheSpec { 16 | 17 | private Description description; 18 | 19 | @Before 20 | public void before() throws Exception { 21 | this.description = new Spectrum(Fixture.getSpecWithPassingAndFailingTests()).getDescription(); 22 | } 23 | 24 | @Test 25 | public void thereAreFiveTests() throws Exception { 26 | assertThat(getFirstContext().getChildren(), hasSize(5)); 27 | } 28 | 29 | @Test 30 | public void theTestsGetTheirClassNameFromTheContainingDescribeBlock() throws Exception { 31 | for (final Description testDescription : getFirstContext().getChildren()) { 32 | assertThat(testDescription.getClassName(), 33 | is("a spec with three passing and two failing tests")); 34 | } 35 | } 36 | 37 | @Test 38 | public void theTestsAreInDeclarationOrder() throws Exception { 39 | final ArrayList testDescriptions = getFirstContext().getChildren(); 40 | assertThat(testDescriptions.get(0).getMethodName(), is("fails test 1")); 41 | assertThat(testDescriptions.get(1).getMethodName(), is("passes test 2")); 42 | assertThat(testDescriptions.get(2).getMethodName(), is("passes test 3")); 43 | assertThat(testDescriptions.get(3).getMethodName(), is("fails test 4")); 44 | assertThat(testDescriptions.get(4).getMethodName(), is("passes test 5")); 45 | } 46 | 47 | private Description getFirstContext() { 48 | return this.description.getChildren().get(0); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/passing/and/failing/tests/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.passing.and.failing.tests; 2 | 3 | import static matchers.IsFailure.failure; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.Matchers.is; 6 | 7 | import com.greghaskins.spectrum.SpectrumHelper; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.Result; 12 | import org.junit.runner.notification.Failure; 13 | 14 | import java.util.List; 15 | 16 | public class WhenRunningTheSpec { 17 | 18 | private Result result; 19 | 20 | @Before 21 | public void before() throws Exception { 22 | this.result = SpectrumHelper.run(Fixture.getSpecWithPassingAndFailingTests()); 23 | } 24 | 25 | @Test 26 | public void fiveTestsAreRun() throws Exception { 27 | assertThat(this.result.getRunCount(), is(5)); 28 | } 29 | 30 | @Test 31 | public void twoTestsFail() throws Exception { 32 | assertThat(this.result.getFailureCount(), is(2)); 33 | } 34 | 35 | @Test 36 | public void theFailuresDescribeWhatWentWrong() throws Exception { 37 | final List failures = this.result.getFailures(); 38 | assertThat(failures.get(0), 39 | is(failure("fails test 1", AssertionError.class, "failure message one"))); 40 | assertThat(failures.get(1), 41 | is(failure("fails test 4", Exception.class, "failure message four"))); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/given/a/spec/with/passing/and/failing/tests/WhenRunningTheTests.java: -------------------------------------------------------------------------------- 1 | package given.a.spec.with.passing.and.failing.tests; 2 | 3 | import static org.mockito.ArgumentMatchers.any; 4 | import static org.mockito.Mockito.mock; 5 | 6 | import com.greghaskins.spectrum.Spectrum; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.Description; 11 | import org.junit.runner.Runner; 12 | import org.junit.runner.notification.RunNotifier; 13 | import org.mockito.InOrder; 14 | import org.mockito.Mockito; 15 | 16 | public class WhenRunningTheTests { 17 | 18 | private static final String CONTEXT_NAME = "a spec with three passing and two failing tests"; 19 | 20 | private RunNotifier runNotifier; 21 | 22 | @Before 23 | public void before() throws Exception { 24 | final Runner runner = new Spectrum(Fixture.getSpecWithPassingAndFailingTests()); 25 | this.runNotifier = mock(RunNotifier.class); 26 | 27 | runner.run(this.runNotifier); 28 | } 29 | 30 | @Test 31 | public void theStartFailureAndFinishedNotificationsAreFiredForFailingTests() throws Exception { 32 | final Description descriptionOfFailingTest = 33 | Description.createTestDescription(CONTEXT_NAME, "fails test 1"); 34 | 35 | final InOrder inOrder = Mockito.inOrder(this.runNotifier); 36 | inOrder.verify(this.runNotifier).fireTestStarted(descriptionOfFailingTest); 37 | inOrder.verify(this.runNotifier).fireTestFailure(any()); 38 | inOrder.verify(this.runNotifier).fireTestFinished(descriptionOfFailingTest); 39 | } 40 | 41 | @Test 42 | public void theStartAndFinishedNotificationsAreFiredForPassingTests() throws Exception { 43 | final Description descriptionOfPassingTest = 44 | Description.createTestDescription(CONTEXT_NAME, "passes test 3"); 45 | 46 | final InOrder inOrder = Mockito.inOrder(this.runNotifier); 47 | inOrder.verify(this.runNotifier).fireTestStarted(descriptionOfPassingTest); 48 | inOrder.verify(this.runNotifier).fireTestFinished(descriptionOfPassingTest); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/given/an/empty/spec/Fixture.java: -------------------------------------------------------------------------------- 1 | package given.an.empty.spec; 2 | 3 | class Fixture { 4 | 5 | public static Class getEmptySpec() { 6 | class Spec { 7 | { 8 | 9 | } 10 | } 11 | 12 | return Spec.class; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/given/an/empty/spec/WhenRunningTheSpec.java: -------------------------------------------------------------------------------- 1 | package given.an.empty.spec; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.is; 5 | 6 | import org.junit.Test; 7 | import org.junit.runner.Result; 8 | 9 | public class WhenRunningTheSpec { 10 | 11 | @Test 12 | public void theRunCountIsZero() throws Exception { 13 | final Result result = com.greghaskins.spectrum.SpectrumHelper.run(Fixture.getEmptySpec()); 14 | assertThat(result.getRunCount(), is(0)); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/given/implementation/of/junit/StubJUnitFrameworkMethod.java: -------------------------------------------------------------------------------- 1 | package given.implementation.of.junit; 2 | 3 | import static com.greghaskins.spectrum.internal.junit.StubJUnitFrameworkMethod.stubFrameworkMethod; 4 | 5 | import org.junit.Test; 6 | import org.junit.runners.model.FrameworkMethod; 7 | 8 | public class StubJUnitFrameworkMethod { 9 | @Test 10 | public void stubCanBeRetrievedAndUsed() throws Throwable { 11 | FrameworkMethod method = stubFrameworkMethod(); 12 | method.invokeExplosively( 13 | new com.greghaskins.spectrum.internal.junit.StubJUnitFrameworkMethod.Stub()); 14 | } 15 | 16 | @Test(expected = RuntimeException.class) 17 | public void cannotMakeFrameworkMethodOfJustAnything() throws Throwable { 18 | stubFrameworkMethod( 19 | com.greghaskins.spectrum.internal.junit.StubJUnitFrameworkMethod.Stub.class, 20 | "notrealmethod"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/junit/rule/ExampleMethodRule.java: -------------------------------------------------------------------------------- 1 | package junit.rule; 2 | 3 | import org.junit.rules.MethodRule; 4 | import org.junit.runners.model.FrameworkMethod; 5 | import org.junit.runners.model.Statement; 6 | 7 | /** 8 | * Method rule we can test with. 9 | */ 10 | public class ExampleMethodRule implements MethodRule { 11 | private int count = 0; 12 | 13 | public int getCount() { 14 | return count; 15 | } 16 | 17 | @Override 18 | public Statement apply(Statement base, FrameworkMethod method, Object target) { 19 | count++; 20 | 21 | return base; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/junit/rule/ExampleRule.java: -------------------------------------------------------------------------------- 1 | package junit.rule; 2 | 3 | import org.junit.rules.TestRule; 4 | import org.junit.runner.Description; 5 | import org.junit.runners.model.Statement; 6 | 7 | 8 | /** 9 | * Testable junit.rule. 10 | */ 11 | public class ExampleRule implements TestRule { 12 | private int count = 0; 13 | 14 | @Override 15 | public Statement apply(Statement base, Description description) { 16 | count++; 17 | 18 | return base; 19 | } 20 | 21 | public int getCount() { 22 | return count; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/junit/spring/SomeComponent.java: -------------------------------------------------------------------------------- 1 | package junit.spring; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * A Spring Component. 7 | */ 8 | @Component 9 | public class SomeComponent { 10 | private String state = ""; 11 | 12 | public String getState() { 13 | return state; 14 | } 15 | 16 | public void setState(String state) { 17 | this.state = state; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/junit/spring/SomeService.java: -------------------------------------------------------------------------------- 1 | package junit.spring; 2 | 3 | /** 4 | * Some service bean. 5 | */ 6 | public interface SomeService { 7 | String getGreeting(); 8 | 9 | SomeComponent getComponent(); 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/junit/spring/SomeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package junit.spring; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | 6 | /** 7 | * Implementation of SomeService Bean. 8 | */ 9 | @Service 10 | public class SomeServiceImpl implements SomeService { 11 | @Autowired 12 | private SomeComponent someComponent; 13 | 14 | @Override 15 | public String getGreeting() { 16 | return "Hello world!"; 17 | } 18 | 19 | @Override 20 | public SomeComponent getComponent() { 21 | return someComponent; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/junit/spring/SpringConfig.java: -------------------------------------------------------------------------------- 1 | package junit.spring; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | /** 7 | * A Spring Configuration Bean which just finds beans in this package. 8 | */ 9 | @Configuration 10 | @ComponentScan(basePackages = "junit.spring") 11 | public class SpringConfig { 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/matchers/IsFailure.java: -------------------------------------------------------------------------------- 1 | package matchers; 2 | 3 | import org.hamcrest.Description; 4 | import org.hamcrest.Matcher; 5 | import org.hamcrest.TypeSafeDiagnosingMatcher; 6 | import org.junit.runner.notification.Failure; 7 | 8 | public class IsFailure extends TypeSafeDiagnosingMatcher { 9 | 10 | public static Matcher failure(final String methodName, 11 | final Class exceptionType, final String failureMessage) { 12 | return new IsFailure(methodName, exceptionType, failureMessage); 13 | } 14 | 15 | private final Class exceptionType; 16 | private final String failureMessage; 17 | private final String methodName; 18 | 19 | IsFailure(final String methodName, final Class exceptionType, 20 | final String failureMessage) { 21 | this.exceptionType = exceptionType; 22 | this.failureMessage = failureMessage; 23 | this.methodName = methodName; 24 | } 25 | 26 | @Override 27 | protected boolean matchesSafely(final Failure item, final Description mismatchDescription) { 28 | final String actualMethodName = getMethodName(item); 29 | final Throwable exception = item.getException(); 30 | final Class actualExceptionType = 31 | exception == null ? null : exception.getClass(); 32 | final String actualMessage = exception == null ? null : item.getMessage(); 33 | 34 | describeTo(mismatchDescription, actualMethodName, actualExceptionType, actualMessage); 35 | 36 | return this.methodName.equals(actualMethodName) 37 | && this.exceptionType.isAssignableFrom(actualExceptionType) 38 | && this.failureMessage.equals(actualMessage); 39 | } 40 | 41 | private String getMethodName(final Failure failure) { 42 | final String actualMethodName; 43 | if (failure.getDescription() == null) { 44 | actualMethodName = null; 45 | } else { 46 | actualMethodName = failure.getDescription().getMethodName(); 47 | } 48 | 49 | return actualMethodName; 50 | } 51 | 52 | @Override 53 | public void describeTo(final Description description) { 54 | describeTo(description, this.methodName, this.exceptionType, this.failureMessage); 55 | } 56 | 57 | private void describeTo(final Description description, final String methodName, 58 | final Class exceptionType, final String failureMessage) { 59 | description.appendText("Failure with test name ").appendValue(methodName) 60 | .appendText(" with exception type ").appendValue(exceptionType).appendText(" and message ") 61 | .appendValue(failureMessage); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/specs/BlockConfigurationSpecs.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.Configure.ignore; 4 | import static com.greghaskins.spectrum.Configure.with; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 6 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 7 | import static com.greghaskins.spectrum.dsl.specification.Specification.let; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.Matchers.is; 10 | 11 | import com.greghaskins.spectrum.Spectrum; 12 | import com.greghaskins.spectrum.SpectrumHelper; 13 | import com.greghaskins.spectrum.internal.configuration.BlockConfiguration; 14 | 15 | import org.junit.runner.Result; 16 | import org.junit.runner.RunWith; 17 | 18 | import java.util.function.Supplier; 19 | 20 | /** 21 | * Demonstrate how to focus and ignore specs using 22 | * {@link BlockConfiguration}. 23 | */ 24 | @RunWith(Spectrum.class) 25 | public class BlockConfigurationSpecs { 26 | { 27 | describe("The ignore() configuration", () -> { 28 | 29 | describe("at the suite level", () -> { 30 | Supplier result = let(() -> SpectrumHelper.run(() -> { 31 | 32 | describe("Has ignored suite", with(ignore(), () -> { 33 | it("will not run this spec", () -> { 34 | }); 35 | it("or this spec", () -> { 36 | }); 37 | })); 38 | })); 39 | 40 | it("marks all its specs as ignored", () -> { 41 | assertThat(result.get().getIgnoreCount(), is(2)); 42 | }); 43 | 44 | }); 45 | 46 | describe("at the spec level", () -> { 47 | 48 | Supplier result = let(() -> SpectrumHelper.run(() -> { 49 | 50 | it("is not ignored", () -> { 51 | }); 52 | 53 | it("is ignored", with(ignore(), () -> { 54 | })); 55 | 56 | it("is ignored for a reason", with(ignore("not important for this release"), () -> { 57 | })); 58 | 59 | it("is a block ignored as a block", ignore(() -> { 60 | })); 61 | 62 | it("is a block ignored as a block for a reason", ignore("Not ready yet", () -> { 63 | })); 64 | 65 | })); 66 | 67 | it("marks those specs as ignored", () -> { 68 | assertThat(result.get().getIgnoreCount(), is(4)); 69 | }); 70 | }); 71 | 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/specs/GherkinExampleSpecs.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.and; 4 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.feature; 5 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.given; 6 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.scenario; 7 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.then; 8 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.when; 9 | import static org.hamcrest.core.Is.is; 10 | import static org.junit.Assert.assertThat; 11 | 12 | import com.greghaskins.spectrum.Spectrum; 13 | import com.greghaskins.spectrum.Variable; 14 | 15 | import org.junit.runner.RunWith; 16 | 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | 19 | @RunWith(Spectrum.class) 20 | public class GherkinExampleSpecs { 21 | { 22 | feature("Gherkin-like test DSL", () -> { 23 | 24 | scenario("using given-when-then steps", () -> { 25 | final AtomicInteger integer = new AtomicInteger(); 26 | given("we start with a given", () -> { 27 | integer.set(12); 28 | }); 29 | when("we have a when to execute the system", () -> { 30 | integer.incrementAndGet(); 31 | }); 32 | then("we can assert the outcome", () -> { 33 | assertThat(integer.get(), is(13)); 34 | }); 35 | }); 36 | 37 | scenario("using variables within the scenario to pass data between steps", () -> { 38 | final Variable theData = new Variable<>(); 39 | 40 | given("the data is set", () -> { 41 | theData.set("Hello"); 42 | }); 43 | 44 | when("the data is modified", () -> { 45 | theData.set(theData.get() + " world!"); 46 | }); 47 | 48 | then("the data can be seen with the new value", () -> { 49 | assertThat(theData.get(), is("Hello world!")); 50 | }); 51 | 52 | and("the data is still available in subsequent steps", () -> { 53 | assertThat(theData.get(), is("Hello world!")); 54 | }); 55 | }); 56 | 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/specs/JUnitRuleExample.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.Configure.junitMixin; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 6 | import static org.hamcrest.core.Is.is; 7 | import static org.junit.Assert.assertNotNull; 8 | import static org.junit.Assert.assertThat; 9 | 10 | import com.greghaskins.spectrum.Spectrum; 11 | 12 | import org.junit.BeforeClass; 13 | import org.junit.Rule; 14 | import org.junit.rules.TemporaryFolder; 15 | import org.junit.runner.RunWith; 16 | 17 | import java.io.File; 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | import java.util.function.Supplier; 21 | 22 | @RunWith(Spectrum.class) 23 | public class JUnitRuleExample { 24 | // mixins for the Spectrum native style of mixin 25 | public static class TempFolderRuleMixin { 26 | @Rule 27 | public TemporaryFolder tempFolderRule = new TemporaryFolder(); 28 | } 29 | 30 | // alternative morphology of providing a rule - see http://junit.org/junit4/javadoc/4.12/org/junit/Rule.html 31 | public static class TempFolderRuleProvidedViaMethodMixin { 32 | private TemporaryFolder tempFolderRule = new TemporaryFolder(); 33 | 34 | @Rule 35 | public TemporaryFolder getFolder() { 36 | return tempFolderRule; 37 | } 38 | } 39 | 40 | public static class JUnitBeforeClassExample { 41 | public static String value; 42 | 43 | @BeforeClass 44 | public static void beforeClass() { 45 | value = "Hello world"; 46 | } 47 | } 48 | 49 | // can also use native junit annotations 50 | private static String classValue; 51 | 52 | @BeforeClass 53 | public static void beforeClass() { 54 | classValue = "initialised"; 55 | } 56 | 57 | { 58 | final Set ruleProvidedFoldersSeen = new HashSet<>(); 59 | describe("A spec with a rule mix-in", () -> { 60 | Supplier tempFolderRuleMixin = junitMixin(TempFolderRuleMixin.class); 61 | 62 | it("has access to the rule-provided object at the top level", () -> { 63 | checkCanUseTempFolderAndRecordWhatItWas(ruleProvidedFoldersSeen, tempFolderRuleMixin); 64 | }); 65 | 66 | describe("with a nested set of specs", () -> { 67 | it("can access the rule-provided object within the nested spec", () -> { 68 | checkCanUseTempFolderAndRecordWhatItWas(ruleProvidedFoldersSeen, tempFolderRuleMixin); 69 | }); 70 | it("can access the rule-provided object over and over", () -> { 71 | checkCanUseTempFolderAndRecordWhatItWas(ruleProvidedFoldersSeen, tempFolderRuleMixin); 72 | }); 73 | }); 74 | 75 | it("has received a different instance of the rule-provided object each time", () -> { 76 | assertThat(ruleProvidedFoldersSeen.size(), is(3)); 77 | }); 78 | 79 | describe("with just a beforeClass mixin", () -> { 80 | Supplier mixin = junitMixin(JUnitBeforeClassExample.class); 81 | it("the mixin's before class has been called", () -> { 82 | assertThat(JUnitBeforeClassExample.value, is("Hello world")); 83 | }); 84 | }); 85 | 86 | it("has also initialised a class member owing to a local JUnit annotation", () -> { 87 | assertThat(classValue, is("initialised")); 88 | }); 89 | }); 90 | 91 | describe("A spec with a rule mix-in where the rule is provided by method", () -> { 92 | Supplier tempFolderRuleMixin = 93 | junitMixin(TempFolderRuleProvidedViaMethodMixin.class); 94 | 95 | it("has access to an initialised object", () -> { 96 | assertNotNull(tempFolderRuleMixin.get().getFolder().getRoot()); 97 | }); 98 | }); 99 | } 100 | 101 | private void checkCanUseTempFolderAndRecordWhatItWas(Set filesSeen, 102 | Supplier tempFolderRuleMixin) { 103 | assertNotNull(tempFolderRuleMixin.get().tempFolderRule.getRoot()); 104 | filesSeen.add(tempFolderRuleMixin.get().tempFolderRule.getRoot()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/specs/MockitoSpecJUnitStyle.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.beforeEach; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 6 | import static org.hamcrest.core.Is.is; 7 | import static org.junit.Assert.assertThat; 8 | import static org.mockito.BDDMockito.given; 9 | import static org.mockito.Mockito.never; 10 | import static org.mockito.Mockito.verify; 11 | 12 | import com.greghaskins.spectrum.Configure; 13 | import com.greghaskins.spectrum.Spectrum; 14 | 15 | import org.junit.Rule; 16 | import org.junit.runner.RunWith; 17 | import org.mockito.InjectMocks; 18 | import org.mockito.Mock; 19 | import org.mockito.junit.MockitoJUnit; 20 | import org.mockito.junit.MockitoRule; 21 | 22 | /** 23 | * Demonstration of how to mix metaphors and use Mockito with Spectrum via 24 | * class members. There is only one instance of the test objects 25 | * so {@link org.mockito.InjectMocks} may have unexpected behaviour in some 26 | * complex situations. If this doesn't work use {@link Configure#junitMixin(Class)}. 27 | */ 28 | @RunWith(Spectrum.class) 29 | public class MockitoSpecJUnitStyle { 30 | // Example of a mockable 31 | interface SomeInterface { 32 | String getInput(); 33 | } 34 | 35 | static class SomeClass { 36 | private SomeInterface someInterface; 37 | 38 | public SomeClass(SomeInterface someInterface) { 39 | this.someInterface = someInterface; 40 | } 41 | 42 | public String getResult() { 43 | return someInterface.getInput(); 44 | } 45 | } 46 | 47 | // Test code starts here 48 | 49 | @Mock 50 | private SomeInterface mockInterface; 51 | 52 | @InjectMocks 53 | private SomeClass objectUnderTest; 54 | 55 | @Rule 56 | public MockitoRule rule = MockitoJUnit.rule(); 57 | 58 | 59 | // test specs start here 60 | { 61 | describe("A suite which needs mockito", () -> { 62 | beforeEach(() -> { 63 | given(mockInterface.getInput()).willReturn("Hello world"); 64 | }); 65 | 66 | it("can use the mocks", () -> { 67 | assertThat(objectUnderTest.getResult(), is("Hello world")); 68 | }); 69 | 70 | it("can use the mocks again", () -> { 71 | assertThat(objectUnderTest.getResult(), is("Hello world")); 72 | }); 73 | 74 | it("uses the mock", () -> { 75 | objectUnderTest.getResult(); 76 | verify(mockInterface).getInput(); 77 | }); 78 | 79 | it("gets a fresh mock each time", () -> { 80 | // so the mock has never been called 81 | verify(mockInterface, never()).getInput(); 82 | }); 83 | 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/specs/MockitoSpecWithRuleClasses.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | 4 | import static com.greghaskins.spectrum.Configure.junitMixin; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.beforeEach; 6 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 7 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 8 | import static org.hamcrest.core.Is.is; 9 | import static org.junit.Assert.assertThat; 10 | import static org.mockito.BDDMockito.given; 11 | import static org.mockito.Mockito.never; 12 | import static org.mockito.Mockito.verify; 13 | 14 | import com.greghaskins.spectrum.Spectrum; 15 | 16 | import org.junit.Rule; 17 | import org.junit.runner.RunWith; 18 | import org.mockito.InjectMocks; 19 | import org.mockito.Mock; 20 | import org.mockito.junit.MockitoJUnit; 21 | import org.mockito.junit.MockitoRule; 22 | 23 | import java.util.function.Supplier; 24 | 25 | /** 26 | * Example of using the Mockito JUnit Rule to provide mocks to specs. 27 | */ 28 | @RunWith(Spectrum.class) 29 | public class MockitoSpecWithRuleClasses { 30 | // Example of a mockable 31 | interface SomeInterface { 32 | String getInput(); 33 | } 34 | 35 | static class SomeClass { 36 | private SomeInterface someInterface; 37 | 38 | public SomeClass(SomeInterface someInterface) { 39 | this.someInterface = someInterface; 40 | } 41 | 42 | public String getResult() { 43 | return someInterface.getInput(); 44 | } 45 | } 46 | 47 | // maybe this would be an external class - put inline here for clarity 48 | public static class Mocks { 49 | @Mock 50 | private SomeInterface mockInterface; 51 | 52 | @InjectMocks 53 | private SomeClass objectUnderTest; 54 | 55 | @Rule 56 | public MockitoRule rule = MockitoJUnit.rule(); 57 | } 58 | 59 | // test specs start here 60 | { 61 | describe("A suite which needs mockito", () -> { 62 | Supplier mocksMixin = junitMixin(Mocks.class); 63 | 64 | beforeEach(() -> { 65 | given(mocksMixin.get().mockInterface.getInput()).willReturn("Hello world"); 66 | }); 67 | 68 | it("can use the mocks", () -> { 69 | assertThat(mocksMixin.get().objectUnderTest.getResult(), is("Hello world")); 70 | }); 71 | 72 | it("can use the mocks again", () -> { 73 | assertThat(mocksMixin.get().objectUnderTest.getResult(), is("Hello world")); 74 | }); 75 | 76 | it("gets a fresh mock each time", () -> { 77 | verify(mocksMixin.get().mockInterface, never()).getInput(); 78 | }); 79 | 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/specs/NestingSpec.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 5 | import static matchers.IsFailure.failure; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.Matchers.contains; 8 | import static org.hamcrest.Matchers.is; 9 | import static org.junit.Assert.fail; 10 | 11 | import com.greghaskins.spectrum.Spectrum; 12 | import com.greghaskins.spectrum.SpectrumHelper; 13 | 14 | import org.junit.runner.Result; 15 | import org.junit.runner.RunWith; 16 | 17 | @SuppressWarnings("unchecked") 18 | @RunWith(Spectrum.class) 19 | public class NestingSpec { 20 | { 21 | 22 | describe("A spec with tests and nested contexts", () -> { 23 | 24 | it("runs them in declaration order", () -> { 25 | final Result result = SpectrumHelper.run(getSpecWithTestsAndNestedContextsThatAllFail()); 26 | 27 | assertThat(result.getFailureCount(), is(3)); 28 | assertThat(result.getFailures(), 29 | contains(failure("fails the first test", AssertionError.class, "boom 1"), 30 | failure("fails the second test", AssertionError.class, "boom 2"), 31 | failure("fails the third test", AssertionError.class, "boom 3"))); 32 | }); 33 | 34 | }); 35 | 36 | } 37 | 38 | private static Class getSpecWithTestsAndNestedContextsThatAllFail() { 39 | class Spec { 40 | { 41 | describe("A spec where everything fails", () -> { 42 | 43 | it("fails the first test", () -> { 44 | fail("boom 1"); 45 | }); 46 | 47 | describe("including the inner context", () -> { 48 | 49 | it("fails the second test", () -> { 50 | fail("boom 2"); 51 | }); 52 | 53 | }); 54 | 55 | it("fails the third test", () -> { 56 | fail("boom 3"); 57 | }); 58 | 59 | }); 60 | } 61 | } 62 | 63 | return Spec.class; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/specs/ParameterizedExampleSpecs.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.and; 4 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.example; 5 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.given; 6 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.scenarioOutline; 7 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.then; 8 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.when; 9 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.withExamples; 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.hamcrest.Matchers.equalTo; 12 | import static org.hamcrest.Matchers.is; 13 | 14 | import com.greghaskins.spectrum.Spectrum; 15 | import com.greghaskins.spectrum.Variable; 16 | import com.greghaskins.spectrum.dsl.gherkin.Gherkin; 17 | 18 | import jdk.nashorn.api.scripting.NashornScriptEngine; 19 | import org.junit.runner.RunWith; 20 | 21 | import javax.script.ScriptEngine; 22 | import javax.script.ScriptEngineManager; 23 | import javax.script.ScriptException; 24 | 25 | /** 26 | * Trying out Scenario outline. 27 | */ 28 | @RunWith(Spectrum.class) 29 | public class ParameterizedExampleSpecs { 30 | { 31 | scenarioOutline("Cucumber eating", 32 | (start, eat, remaining) -> { 33 | 34 | Variable me = new Variable<>(); 35 | 36 | given("there are " + start + " cucumbers", () -> { 37 | me.set(new CukeEater(start)); 38 | }); 39 | 40 | when("I eat " + eat + " cucumbers", () -> { 41 | me.get().eatCucumbers(eat); 42 | }); 43 | 44 | then("I should have " + remaining + " cucumbers", () -> { 45 | assertThat(me.get().remainingCucumbers(), is(remaining)); 46 | }); 47 | }, 48 | 49 | withExamples( 50 | example(12, 5, 7), 51 | example(20, 5, 15)) 52 | 53 | ); 54 | 55 | scenarioOutline("Simple calculations", 56 | (expression, expectedResult) -> { 57 | 58 | Variable calculator = new Variable<>(); 59 | Variable result = new Variable<>(); 60 | 61 | given("a calculator", () -> { 62 | calculator.set(new Calculator()); 63 | }); 64 | when("it computes the expression " + expression, () -> { 65 | result.set(calculator.get().compute(expression)); 66 | }); 67 | then("the result is " + expectedResult, () -> { 68 | assertThat(result.get(), is(expectedResult)); 69 | }); 70 | 71 | }, 72 | 73 | withExamples( 74 | example("1 + 1", 2), 75 | example("5 * 9", 45), 76 | example("7 / 2", 3.5))); 77 | 78 | scenarioOutline("different types of parameters", 79 | (foo, bar, baz) -> { 80 | 81 | given("foo is " + foo, () -> { 82 | }); 83 | and("bar is " + bar, () -> { 84 | }); 85 | and("baz is " + baz, () -> { 86 | }); 87 | when("something happens", () -> { 88 | }); 89 | then("it works", () -> { 90 | }); 91 | 92 | }, 93 | 94 | withExamples( 95 | example(1, "boo", 3.14), 96 | example(1, "yay", 4.2)) 97 | 98 | 99 | ); 100 | 101 | 102 | 103 | scenarioOutline("with two parameters, just to see", 104 | (foo, bar) -> { 105 | 106 | given("blah " + foo, () -> { 107 | }); 108 | when(bar + " - blerg", () -> { 109 | }); 110 | then("something", () -> { 111 | }); 112 | 113 | }, 114 | 115 | withExamples( 116 | example("hey", 3.14), 117 | example("hi", 6.2), 118 | example("bye", -1.5)) 119 | 120 | ); 121 | } 122 | 123 | // dummy class under test 124 | static class CukeEater { 125 | 126 | private int amount; 127 | 128 | public CukeEater(int amount) { 129 | this.amount = amount; 130 | } 131 | 132 | public int remainingCucumbers() { 133 | return this.amount; 134 | } 135 | 136 | public void eatCucumbers(int number) { 137 | this.amount -= number; 138 | } 139 | 140 | } 141 | 142 | // another dummy class under test 143 | static class Calculator { 144 | 145 | private final ScriptEngine engine; 146 | 147 | public Calculator() { 148 | this.engine = new ScriptEngineManager().getEngineByName("nashorn"); 149 | } 150 | 151 | public Number compute(String expression) throws Exception { 152 | return (Number) this.engine.eval(expression); 153 | } 154 | 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/test/java/specs/PendingSpec.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.beforeEach; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 6 | import static com.greghaskins.spectrum.dsl.specification.Specification.pending; 7 | import static org.junit.Assert.assertFalse; 8 | 9 | import com.greghaskins.spectrum.Spectrum; 10 | import com.greghaskins.spectrum.SpectrumHelper; 11 | import com.greghaskins.spectrum.Variable; 12 | 13 | import org.junit.runner.RunWith; 14 | 15 | /** 16 | * Use of the pending function to make a spec pending. 17 | */ 18 | @RunWith(Spectrum.class) 19 | public class PendingSpec { 20 | { 21 | Variable hasSpecRun = new Variable<>(false); 22 | 23 | describe("Jasmine and RSpec style pending", () -> { 24 | 25 | beforeEach(() -> SpectrumHelper.run(() -> { 26 | it("sets to ignored a spec with pending in it", () -> { 27 | pending(); 28 | hasSpecRun.set(true); 29 | }); 30 | 31 | it("can have a message to show why it is pending", () -> { 32 | pending("not likely to be implemented for a while"); 33 | hasSpecRun.set(true); 34 | }); 35 | })); 36 | 37 | it("did not run any specs up to now", () -> { 38 | assertFalse(hasSpecRun.get()); 39 | }); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/specs/ReadmeSpecs.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.and; 4 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.feature; 5 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.given; 6 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.scenario; 7 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.then; 8 | import static com.greghaskins.spectrum.dsl.gherkin.Gherkin.when; 9 | import static com.greghaskins.spectrum.dsl.specification.Specification.afterEach; 10 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 11 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.Matchers.contains; 14 | import static org.hamcrest.Matchers.is; 15 | 16 | import com.greghaskins.spectrum.Spectrum; 17 | import com.greghaskins.spectrum.Variable; 18 | 19 | import org.junit.runner.RunWith; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | @RunWith(Spectrum.class) 25 | public class ReadmeSpecs { 26 | 27 | { 28 | describe("A list", () -> { 29 | 30 | List list = new ArrayList<>(); 31 | 32 | afterEach(list::clear); 33 | 34 | it("should be empty by default", () -> { 35 | assertThat(list.size(), is(0)); 36 | }); 37 | 38 | it("should be able to add items", () -> { 39 | list.add("foo"); 40 | list.add("bar"); 41 | 42 | assertThat(list, contains("foo", "bar")); 43 | }); 44 | 45 | }); 46 | 47 | feature("Lists", () -> { 48 | 49 | scenario("adding items", () -> { 50 | 51 | Variable> list = new Variable<>(); 52 | 53 | given("an empty list", () -> { 54 | list.set(new ArrayList<>()); 55 | }); 56 | 57 | when("you add the item 'foo'", () -> { 58 | list.get().add("foo"); 59 | }); 60 | 61 | and("you add the item 'bar'", () -> { 62 | list.get().add("bar"); 63 | }); 64 | 65 | then("it contains both foo and bar", () -> { 66 | assertThat(list.get(), contains("foo", "bar")); 67 | }); 68 | 69 | }); 70 | 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/specs/RunnerSpec.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.is; 7 | 8 | import com.greghaskins.spectrum.Spectrum; 9 | import com.greghaskins.spectrum.SpectrumHelper; 10 | 11 | import org.junit.runner.Result; 12 | import org.junit.runner.RunWith; 13 | 14 | @RunWith(Spectrum.class) 15 | public class RunnerSpec { 16 | { 17 | 18 | describe("Contexts with no tests", () -> { 19 | 20 | it("are ignored", () -> { 21 | final Result result = SpectrumHelper.run(getSpecWithNoTests()); 22 | assertThat(result.getIgnoreCount(), is(2)); 23 | }); 24 | 25 | }); 26 | 27 | } 28 | 29 | private static final Class getSpecWithNoTests() { 30 | class Spec { 31 | { 32 | 33 | it("has a test in the outer context, so that doesn't get ignored", () -> { 34 | 35 | }); 36 | 37 | describe("no tests by itself or in children, will be ignored", () -> { 38 | 39 | describe("no tests either, will be ignored", () -> { 40 | 41 | }); 42 | 43 | }); 44 | 45 | } 46 | } 47 | 48 | return Spec.class; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/specs/SpringSpecJUnitStyle.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 5 | import static org.hamcrest.core.Is.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | import com.greghaskins.spectrum.Spectrum; 9 | 10 | import junit.spring.SomeService; 11 | import junit.spring.SpringConfig; 12 | import org.junit.ClassRule; 13 | import org.junit.Rule; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.test.context.ContextConfiguration; 17 | import org.springframework.test.context.junit4.rules.SpringClassRule; 18 | import org.springframework.test.context.junit4.rules.SpringMethodRule; 19 | 20 | /** 21 | * Example of how to mix Spring Test, JUnit and Spectrum. 22 | */ 23 | @RunWith(Spectrum.class) 24 | @ContextConfiguration(classes = {SpringConfig.class}) 25 | public class SpringSpecJUnitStyle { 26 | @ClassRule 27 | public static final SpringClassRule classRule = new SpringClassRule(); 28 | 29 | @Rule 30 | public SpringMethodRule methodRule = new SpringMethodRule(); 31 | 32 | @Autowired 33 | SomeService someService; 34 | 35 | { 36 | describe("A spring specification", () -> { 37 | it("can access an autowired spring bean from the test object", () -> { 38 | assertThat(someService.getGreeting(), is("Hello world!")); 39 | }); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/specs/SpringSpecWithRuleClasses.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.Configure.junitMixin; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 6 | import static org.hamcrest.core.Is.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | import com.greghaskins.spectrum.Spectrum; 10 | 11 | import junit.spring.SomeService; 12 | import junit.spring.SpringConfig; 13 | import org.junit.ClassRule; 14 | import org.junit.Rule; 15 | import org.junit.runner.RunWith; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.test.context.ContextConfiguration; 18 | import org.springframework.test.context.junit4.rules.SpringClassRule; 19 | import org.springframework.test.context.junit4.rules.SpringMethodRule; 20 | 21 | import java.util.function.Supplier; 22 | 23 | /** 24 | * Example of how to wire in Spring objects. 25 | */ 26 | @RunWith(Spectrum.class) 27 | public class SpringSpecWithRuleClasses { 28 | /** 29 | * Note - you might usually declare this in its own file. This Mixin has the same structure as a 30 | * JUnit class with rules. 31 | */ 32 | @ContextConfiguration(classes = {SpringConfig.class}) 33 | public static class Mixin { 34 | @ClassRule 35 | public static final SpringClassRule classRule = new SpringClassRule(); 36 | 37 | @Rule 38 | public SpringMethodRule methodRule = new SpringMethodRule(); 39 | 40 | @Autowired 41 | SomeService someService; 42 | } 43 | 44 | // Normal testing starts here 45 | { 46 | describe("A spring specification", () -> { 47 | Supplier springMixin = junitMixin(Mixin.class); 48 | 49 | it("can access a spring bean from the mixin object", () -> { 50 | assertThat(springMixin.get().someService.getGreeting(), is("Hello world!")); 51 | }); 52 | 53 | it("can access the bean a second time", () -> { 54 | assertThat(springMixin.get().someService.getGreeting(), is("Hello world!")); 55 | }); 56 | 57 | it("can access a dependency of the bean", () -> { 58 | assertThat(springMixin.get().someService.getComponent().getState(), is("")); 59 | }); 60 | 61 | it("when we write to the dependency", () -> { 62 | springMixin.get().someService.getComponent().setState("Bob"); 63 | }); 64 | 65 | it("the object stays set in Spring", () -> { 66 | assertThat(springMixin.get().someService.getComponent().getState(), is("Bob")); 67 | }); 68 | 69 | describe("a child suite", () -> { 70 | it("also has access to the spring beans", () -> { 71 | assertThat(springMixin.get().someService.getComponent().getState(), is("Bob")); 72 | }); 73 | }); 74 | 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/specs/TimeoutSpecs.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.Configure.timeout; 4 | import static com.greghaskins.spectrum.Configure.with; 5 | import static com.greghaskins.spectrum.Spectrum.*; 6 | import static java.time.Duration.ofMillis; 7 | import static java.time.Duration.ofMinutes; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.Matchers.is; 10 | 11 | import com.greghaskins.spectrum.Spectrum; 12 | import com.greghaskins.spectrum.SpectrumHelper; 13 | 14 | import org.junit.runner.Result; 15 | import org.junit.runner.RunWith; 16 | 17 | import java.util.function.Supplier; 18 | 19 | @RunWith(Spectrum.class) 20 | public class TimeoutSpecs { 21 | { 22 | describe("A suite with timeouts", () -> { 23 | it("will allow things to pass if they run quicker than the timeout", () -> { 24 | final Result result = SpectrumHelper.run(() -> { 25 | describe("Suite with generous timeout", with(timeout(ofMillis(1000)), () -> { 26 | it("has no problem when test lasts a tiny bit", () -> { 27 | Thread.sleep(1); 28 | }); 29 | })); 30 | }); 31 | assertThat(result.getFailureCount(), is(0)); 32 | }); 33 | 34 | it("will fail a single test that exceeds its timeout", () -> { 35 | final Result result = SpectrumHelper.run(() -> { 36 | describe("Suite with low timeout", with(timeout(ofMillis(1)), () -> { 37 | it("has spec oversleeps and fails", () -> { 38 | Thread.sleep(1000); 39 | }); 40 | })); 41 | }); 42 | assertThat(result.getFailureCount(), is(1)); 43 | }); 44 | 45 | it("will report a timing out test correctly when there are surrounding hooks", () -> { 46 | // if hooks were run in within the timeout's envelope, then when the child 47 | // is killed for timing out, there would be "bleed" of reporting 48 | final Result result = SpectrumHelper.run(() -> { 49 | describe("Suite with low timeout", with(timeout(ofMillis(1)), () -> { 50 | Supplier let = let(() -> "Hello world"); 51 | beforeEach(() -> { 52 | // deliberately blank before each - just for ensuring a hook is present 53 | }); 54 | it("has spec oversleeps and fails", () -> { 55 | assertThat(let.get(), is("Hello world")); 56 | Thread.sleep(1000); 57 | }); 58 | })); 59 | }); 60 | assertThat(result.getFailureCount(), is(1)); 61 | }); 62 | 63 | it("will fail the test that fails its timeout and pass the others", () -> { 64 | final Result result = SpectrumHelper.run(() -> { 65 | describe("Suite with timeout", with(timeout(ofMillis(10)), () -> { 66 | it("has spec that takes no time", () -> { 67 | 68 | }); 69 | 70 | it("has spec that oversleeps and fails", () -> { 71 | Thread.sleep(1000); 72 | }); 73 | 74 | it("has another spec that takes no time", () -> { 75 | 76 | }); 77 | })); 78 | }); 79 | assertThat(result.getRunCount(), is(3)); 80 | assertThat(result.getFailures().get(0).getDescription().getMethodName(), 81 | is("has spec that oversleeps and fails")); 82 | assertThat(result.getFailureCount(), is(1)); 83 | }); 84 | 85 | it("will propagate the timeout from parent to child", () -> { 86 | final Result result = SpectrumHelper.run(() -> { 87 | describe("Suite with low timeout", with(timeout(ofMillis(1)), () -> { 88 | describe("with child suite", () -> { 89 | it("oversleeps and fails", () -> { 90 | Thread.sleep(1000); 91 | }); 92 | }); 93 | })); 94 | }); 95 | assertThat(result.getFailureCount(), is(1)); 96 | }); 97 | 98 | it("will allow a lower level to supersede the parent's timeout", () -> { 99 | final Result result = SpectrumHelper.run(() -> { 100 | describe("Suite with low timeout", with(timeout(ofMillis(1)), () -> { 101 | describe("with child suite", () -> { 102 | it("has a more generous timeout", with(timeout(ofMinutes(1)), () -> { 103 | Thread.sleep(100); 104 | })); 105 | }); 106 | })); 107 | }); 108 | assertThat(result.getFailureCount(), is(0)); 109 | }); 110 | }); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/specs/VariableSpecs.java: -------------------------------------------------------------------------------- 1 | package specs; 2 | 3 | import static com.greghaskins.spectrum.dsl.specification.Specification.beforeAll; 4 | import static com.greghaskins.spectrum.dsl.specification.Specification.beforeEach; 5 | import static com.greghaskins.spectrum.dsl.specification.Specification.describe; 6 | import static com.greghaskins.spectrum.dsl.specification.Specification.it; 7 | import static junit.framework.TestCase.assertNull; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.Matchers.is; 10 | 11 | import com.greghaskins.spectrum.Spectrum; 12 | import com.greghaskins.spectrum.Variable; 13 | 14 | import org.junit.runner.RunWith; 15 | 16 | @RunWith(Spectrum.class) 17 | public class VariableSpecs { 18 | { 19 | 20 | describe("The Variable convenience wrapper", () -> { 21 | 22 | final Variable counter = new Variable<>(); 23 | 24 | beforeAll(() -> { 25 | counter.set(0); 26 | }); 27 | 28 | beforeEach(() -> { 29 | final int previousValue = counter.get(); 30 | counter.set(previousValue + 1); 31 | }); 32 | 33 | it("lets you work around Java's requirement that closures only use `final` variables", () -> { 34 | assertThat(counter.get(), is(1)); 35 | }); 36 | 37 | it("can share values across scopes, so use it carefully", () -> { 38 | assertThat(counter.get(), is(2)); 39 | }); 40 | 41 | it("can optionally have an initial value set", () -> { 42 | final Variable name = new Variable<>("Alice"); 43 | assertThat(name.get(), is("Alice")); 44 | }); 45 | 46 | it("has a null value if not specified", () -> { 47 | final Variable name = new Variable<>(); 48 | assertNull(name.get()); 49 | }); 50 | 51 | it("has the same value across threads", () -> { 52 | final Variable outerVariable = new Variable<>("outer"); 53 | final Variable whatWorkerThreadSees = new Variable<>(); 54 | 55 | Thread worker = new Thread(() -> whatWorkerThreadSees.set(outerVariable.get())); 56 | worker.start(); 57 | worker.join(); 58 | 59 | assertThat(whatWorkerThreadSees.get(), is(outerVariable.get())); 60 | }); 61 | 62 | }); 63 | 64 | } 65 | } 66 | --------------------------------------------------------------------------------