spectrum
spectrum copied to clipboard
Feature Request: Support for Shared Examples and Shared Contexts
This may just be something I'm overlooking in the docs -- or that could use an example -- but is there a way to do RSpec-style shared contexts and shared examples?
For example, I just wrote my first test using Spectrum, which looked like this:
@RunWith(Spectrum.class)
public class EnumsTest {
{
describe(".findValueOrThrow", () -> {
context("when given an Enum that has no values", () -> {
final Supplier<Class<EmptyEnum>> enumClass = let(() -> EmptyEnum.class);
it("throws an IllegalArgumentException", () -> {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> {
Enums.findValueOrThrow(enumClass.get(), (value) -> true);
})
.withMessage(
"No `com.rosieapp.util.EnumsTest.EmptyEnum` was found that matched the specified " +
"filter.")
.withNoCause();
});
});
context("when given an Enum that has values", () -> {
final Supplier<Class<Colors>> enumClass = let(() -> Colors.class);
context("when the predicate does not match any of the values", () -> {
final Supplier<Predicate<Colors>> predicate = let(() -> (color) -> false);
it("throws an IllegalArgumentException", () -> {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> {
Enums.findValueOrThrow(enumClass.get(), predicate.get());
})
.withMessage(
"No `com.rosieapp.util.EnumsTest.Colors` was found that matched the specified " +
"filter.")
.withNoCause();
});
});
context("when the predicate matches one of the values", () -> {
final Supplier<Predicate<Colors>> predicate = let(() -> (color) -> color.name().equals("WHITE"));
it("returns the matching value", () -> {
assertThat(Enums.findValueOrThrow(enumClass.get(), predicate.get()), is(Colors.WHITE));
});
});
context("when the predicate matches multiple values", () -> {
final Supplier<Predicate<Colors>> predicate = let(() ->
(color) -> !Arrays.asList("RED", "WHITE").contains(color.name()));
it("returns the first matching value, according to the order within the enum", () -> {
assertThat(Enums.findValueOrThrow(enumClass.get(), predicate.get()), is(Colors.BLUE));
});
});
});
});
}
// ...
}
Two of those scenarios are expected to throw the same exception with nearly the same error message. In RSpec, I'd abstract that out into a shared example group and then control what's provided using let
. If shared example groups are out of the question, what would be the best practice when using Spectrum for this?
We have support for something similar via the Gherkin syntax - https://github.com/greghaskins/spectrum/blob/master/docs/GherkinDSL.md - but could not yet decide how to make it look for the RSpec style syntax.
Can you give an example of how you do it in RSpec, especially relating to the overloading of let
.
Note - this is essentially the same as #80
In our case, the request is a bit different than #80. The way we usually handle this in RSpec is by defining a shared example group:
class Cat
def make_sound
"Meow!"
end
end
class Dog
def make_sound
"Woof!"
end
end
shared_examples_for 'an animal' do
it 'makes a sound' do
expect(animal.make_sound).not_to be_nil
end
end
describe Cat do
let(:animal) { Cat.new }
it_behaves_like 'an animal'
end
describe Dog do
let(:animal) { Dog.new }
it_behaves_like 'an animal'
end
I was able to get a workable but verbose solution using a functional interface and lambda declared in the test. The lambdas are invoked from within the context of each overall test.
Here's a watered-down example (imagine that each lambda could actually have 5-10 tests, and the only things being passed-in are the suppliers for the values that vary from test to test):
@RunWith(Spectrum.class)
public class MyTest {
{
final Supplier<Object> someObject = let(() -> new Object());
final Supplier<MyObject> testObject = let(() -> new MyObject(object.get()));
final ComparisonSharedExample behavesLikeRegularEquality = (testMethod) -> {
it("returns the match", () -> {
assertThat(testMethod.get()).isSameAs(someObject.get());
});
};
final ComparisonSharedExample behavesLikeOppositeEquality = (testMethod) -> {
it("does not return the match", () -> {
assertThat(testMethod.get()).isNotSameAs(someObject.get());
});
};
describe("#compare1", () -> {
behavesLikeRegularEquality.run(
() -> {
return testObject.get().compare1();
});
});
describe("#compare2", () -> {
behavesLikeOppositeEquality.run(
() -> {
return testObject.get().compare2();
});
});
describe("#compare3", () -> {
behavesLikeRegularEquality.run(
() -> {
return testObject.get().compare3();
});
});
describe("#compare4", () -> {
behavesLikeOppositeEquality.run(
() -> {
return testObject.get().compare4();
});
});
}
interface ComparisonSharedExample {
void run(final Runnable testMethod);
}
}