diff --git a/junit/pom.xml b/junit/pom.xml
index cfc29a7f37..90fbfd1b0d 100644
--- a/junit/pom.xml
+++ b/junit/pom.xml
@@ -26,5 +26,11 @@
mockito-core
test
+
+
+ org.hamcrest
+ hamcrest-library
+ test
+
diff --git a/junit/src/main/java/cucumber/api/junit/Cucumber.java b/junit/src/main/java/cucumber/api/junit/Cucumber.java
index baac3b8707..d2df5b122c 100644
--- a/junit/src/main/java/cucumber/api/junit/Cucumber.java
+++ b/junit/src/main/java/cucumber/api/junit/Cucumber.java
@@ -5,19 +5,18 @@
import cucumber.api.event.TestRunFinished;
import cucumber.api.event.TestRunStarted;
import cucumber.runner.EventBus;
+import cucumber.runner.ThreadLocalRunnerSupplier;
import cucumber.runner.TimeService;
+import cucumber.runner.TimeServiceEventBus;
import cucumber.runtime.BackendModuleBackendSupplier;
import cucumber.runtime.BackendSupplier;
-import cucumber.runner.TimeServiceEventBus;
import cucumber.runtime.ClassFinder;
-import cucumber.runtime.RuntimeOptions;
import cucumber.runtime.FeaturePathFeatureSupplier;
+import cucumber.runtime.RuntimeOptions;
+import cucumber.runtime.RuntimeOptionsFactory;
import cucumber.runtime.filter.Filters;
-import cucumber.runtime.formatter.Plugins;
import cucumber.runtime.formatter.PluginFactory;
-import cucumber.runtime.model.FeatureLoader;
-import cucumber.runner.ThreadLocalRunnerSupplier;
-import cucumber.runtime.RuntimeOptionsFactory;
+import cucumber.runtime.formatter.Plugins;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.io.ResourceLoaderClassFinder;
@@ -25,6 +24,7 @@
import cucumber.runtime.junit.FeatureRunner;
import cucumber.runtime.junit.JUnitOptions;
import cucumber.runtime.model.CucumberFeature;
+import cucumber.runtime.model.FeatureLoader;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
@@ -47,7 +47,7 @@
* @CucumberOptions(plugin = "pretty")
* public class RunCukesTest {
* }
-Fail *
+ *
*
* By default Cucumber will look for {@code .feature} and glue files on the classpath, using the same resource
* path as the annotated class. For example, if the annotated class is {@code com.example.RunCucumber} then
@@ -63,11 +63,11 @@
* @see CucumberOptions
*/
public class Cucumber extends ParentRunner {
- private final List children = new ArrayList();
+ private final List children = new ArrayList<>();
private final EventBus bus;
private final ThreadLocalRunnerSupplier runnerSupplier;
- private final Filters filters;
- private final JUnitOptions junitOptions;
+ private final List features;
+ private final Plugins plugins;
/**
* Constructor called by JUnit.
@@ -77,35 +77,36 @@ public class Cucumber extends ParentRunner {
*/
public Cucumber(Class clazz) throws InitializationError {
super(clazz);
- ClassLoader classLoader = clazz.getClassLoader();
Assertions.assertNoCucumberAnnotatedMethods(clazz);
+ // Parse the options early to provide fast feedback about invalid options
RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(clazz);
RuntimeOptions runtimeOptions = runtimeOptionsFactory.create();
+ JUnitOptions junitOptions = new JUnitOptions(runtimeOptions.isStrict(), runtimeOptions.getJunitOptions());
+ ClassLoader classLoader = clazz.getClassLoader();
ResourceLoader resourceLoader = new MultiLoader(classLoader);
+ ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
+
+ // Parse the features early. Don't proceed when there are lexer errors
FeatureLoader featureLoader = new FeatureLoader(resourceLoader);
FeaturePathFeatureSupplier featureSupplier = new FeaturePathFeatureSupplier(featureLoader, runtimeOptions);
- // Parse the features early. Don't proceed when there are lexer errors
- final List features = featureSupplier.get();
+ this.features = featureSupplier.get();
- ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
- BackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions);
+ // Create plugins after feature parsing to avoid the creation of empty files on lexer errors.
this.bus = new TimeServiceEventBus(TimeService.SYSTEM);
- Plugins plugins = new Plugins(classLoader, new PluginFactory(), bus, runtimeOptions);
+ this.plugins = new Plugins(classLoader, new PluginFactory(), bus, runtimeOptions);
+
+
+ BackendSupplier backendSupplier = new BackendModuleBackendSupplier(resourceLoader, classFinder, runtimeOptions);
this.runnerSupplier = new ThreadLocalRunnerSupplier(runtimeOptions, bus, backendSupplier);
- this.filters = new Filters(runtimeOptions);
- this.junitOptions = new JUnitOptions(runtimeOptions.isStrict(), runtimeOptions.getJunitOptions());
- final StepDefinitionReporter stepDefinitionReporter = plugins.stepDefinitionReporter();
-
- // Start the run before reading the features.
- // Allows the test source read events to be broadcast properly
- bus.send(new TestRunStarted(bus.getTime()));
- for (CucumberFeature feature : features) {
- feature.sendTestSourceRead(bus);
+ Filters filters = new Filters(runtimeOptions);
+ for (CucumberFeature cucumberFeature : features) {
+ FeatureRunner featureRunner = new FeatureRunner(cucumberFeature, filters, runnerSupplier, junitOptions);
+ if (!featureRunner.isEmpty()) {
+ children.add(featureRunner);
+ }
}
- runnerSupplier.get().reportStepDefinitions(stepDefinitionReporter);
- addChildren(features);
}
@Override
@@ -125,22 +126,27 @@ protected void runChild(FeatureRunner child, RunNotifier notifier) {
@Override
protected Statement childrenInvoker(RunNotifier notifier) {
- final Statement features = super.childrenInvoker(notifier);
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- features.evaluate();
- bus.send(new TestRunFinished(bus.getTime()));
- }
- };
+ Statement runFeatures = super.childrenInvoker(notifier);
+ return new RunCucumber(runFeatures);
}
- private void addChildren(List cucumberFeatures) throws InitializationError {
- for (CucumberFeature cucumberFeature : cucumberFeatures) {
- FeatureRunner featureRunner = new FeatureRunner(cucumberFeature, filters, runnerSupplier, junitOptions);
- if (!featureRunner.isEmpty()) {
- children.add(featureRunner);
+ class RunCucumber extends Statement {
+ private final Statement runFeatures;
+
+ RunCucumber(Statement runFeatures) {
+ this.runFeatures = runFeatures;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ bus.send(new TestRunStarted(bus.getTime()));
+ for (CucumberFeature feature : features) {
+ feature.sendTestSourceRead(bus);
}
+ StepDefinitionReporter stepDefinitionReporter = plugins.stepDefinitionReporter();
+ runnerSupplier.get().reportStepDefinitions(stepDefinitionReporter);
+ runFeatures.evaluate();
+ bus.send(new TestRunFinished(bus.getTime()));
}
}
}
diff --git a/junit/src/test/java/cucumber/runtime/junit/InvokeMethodsAroundEventsTest.java b/junit/src/test/java/cucumber/runtime/junit/InvokeMethodsAroundEventsTest.java
new file mode 100644
index 0000000000..fabe16fb82
--- /dev/null
+++ b/junit/src/test/java/cucumber/runtime/junit/InvokeMethodsAroundEventsTest.java
@@ -0,0 +1,76 @@
+package cucumber.runtime.junit;
+
+import cucumber.api.CucumberOptions;
+import cucumber.api.event.ConcurrentEventListener;
+import cucumber.api.event.EventHandler;
+import cucumber.api.event.EventPublisher;
+import cucumber.api.event.TestRunFinished;
+import cucumber.api.event.TestRunStarted;
+import cucumber.api.junit.Cucumber;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.InitializationError;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.junit.Assert.assertThat;
+
+public class InvokeMethodsAroundEventsTest {
+
+ private static final List events = new ArrayList<>();
+
+ private static EventHandler testRunStartedEventHandler = new EventHandler() {
+ @Override
+ public void receive(TestRunStarted event) {
+ events.add("TestRunStarted");
+ }
+ };
+ private static EventHandler testRunFinishedEventHandler = new EventHandler() {
+ @Override
+ public void receive(TestRunFinished event) {
+ events.add("TestRunFinished");
+ }
+ };
+
+ @AfterClass
+ public static void afterClass() {
+ events.clear();
+ }
+
+ @Test
+ public void finds_features_based_on_implicit_package() throws InitializationError {
+ Cucumber cucumber = new Cucumber(BeforeAfterClass.class);
+ cucumber.run(new RunNotifier());
+ assertThat(events, contains("BeforeClass", "TestRunStarted", "TestRunFinished", "AfterClass"));
+ }
+
+ @CucumberOptions(plugin = {"cucumber.runtime.junit.InvokeMethodsAroundEventsTest$TestRunStartedFinishedListener"})
+ public static class BeforeAfterClass {
+
+ @BeforeClass
+ public static void beforeClass() {
+ events.add("BeforeClass");
+
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ events.add("AfterClass");
+ }
+ }
+
+ @SuppressWarnings("unused") // Used as a plugin by BeforeAfterClass
+ public static class TestRunStartedFinishedListener implements ConcurrentEventListener {
+
+ @Override
+ public void setEventPublisher(EventPublisher publisher) {
+ publisher.registerHandlerFor(TestRunStarted.class, testRunStartedEventHandler);
+ publisher.registerHandlerFor(TestRunFinished.class, testRunFinishedEventHandler);
+ }
+
+ }
+}