Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Support for Shared Examples and Shared Contexts #130

Open
GuyPaddock opened this issue Jan 18, 2018 · 4 comments
Open

Feature Request: Support for Shared Examples and Shared Contexts #130

GuyPaddock opened this issue Jan 18, 2018 · 4 comments

Comments

@GuyPaddock
Copy link
Contributor

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?

@GuyPaddock GuyPaddock changed the title Feature request -- shared contexts and examples Feature request -- shared contexts and shared examples Jan 18, 2018
@GuyPaddock GuyPaddock changed the title Feature request -- shared contexts and shared examples Feature request: Shared contexts and shared examples Jan 19, 2018
@GuyPaddock GuyPaddock changed the title Feature request: Shared contexts and shared examples Feature request: Support for shared examples and shared contexts Jan 19, 2018
@GuyPaddock GuyPaddock changed the title Feature request: Support for shared examples and shared contexts Feature Request: Support for Shared Examples and Shared Contexts Jan 19, 2018
@ashleyfrieze
Copy link
Contributor

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.

@ashleyfrieze
Copy link
Contributor

Note - this is essentially the same as #80

@GuyPaddock
Copy link
Contributor Author

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

@GuyPaddock
Copy link
Contributor Author

GuyPaddock commented Jul 3, 2018

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);
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants