diff --git a/cucumber-core/pom.xml b/cucumber-core/pom.xml index 1e2e3b53e6..8376a8b54b 100644 --- a/cucumber-core/pom.xml +++ b/cucumber-core/pom.xml @@ -20,7 +20,6 @@ 2.9.1 2.2 0.2 - 5.6.0 4.4.6 1.0.4 @@ -139,12 +138,6 @@ junit-jupiter test - - org.mockito - mockito-junit-jupiter - ${mockito.version} - test - io.vertx vertx-web diff --git a/cucumber-core/src/test/java/io/cucumber/core/runner/HookTest.java b/cucumber-core/src/test/java/io/cucumber/core/runner/HookTest.java index eb0f1e3055..79b9f59a13 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runner/HookTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/runner/HookTest.java @@ -4,6 +4,8 @@ import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.backend.Snippet; +import io.cucumber.core.backend.TestCaseState; import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.feature.TestFeatureParser; import io.cucumber.core.gherkin.Feature; @@ -12,21 +14,18 @@ import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.snippets.TestSnippet; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.InOrder; +import java.net.URI; import java.time.Clock; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; class HookTest { @@ -44,43 +43,23 @@ class HookTest { */ @Test void after_hooks_execute_before_objects_are_disposed() { - Backend backend = mock(Backend.class); - when(backend.getSnippet()).thenReturn(new TestSnippet()); + final List eventListener = new ArrayList<>(); + final HookDefinition hook = new MockHookDefinition("", "hook-location", eventListener); + Backend backend = new StubBackend(hook, eventListener); ObjectFactory objectFactory = new StubObjectFactory(); - final HookDefinition hook = mock(HookDefinition.class); - when(hook.getLocation()).thenReturn("hook-location"); - when(hook.getTagExpression()).thenReturn(""); - - doAnswer(invocation -> { - Glue glue = invocation.getArgument(0); - glue.addBeforeHook(hook); - return null; - }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); - Runner runner = new Runner(bus, Collections.singleton(backend), objectFactory, runtimeOptions); runner.runPickle(pickle); - InOrder inOrder = inOrder(hook, backend); - inOrder.verify(backend).buildWorld(); - inOrder.verify(hook).execute(ArgumentMatchers.any()); - inOrder.verify(backend).disposeWorld(); + assertLinesMatch(eventListener, List.of("buildWorld", "execute", "disposeWorld")); } @Test void hook_throws_exception_with_name_when_tag_expression_is_invalid() { - Backend backend = mock(Backend.class); - when(backend.getSnippet()).thenReturn(new TestSnippet()); + final List eventListener = new ArrayList<>(); + final HookDefinition hook = new MockHookDefinition("(", "hook-location", eventListener); + Backend backend = new StubBackend(hook, eventListener); ObjectFactory objectFactory = new StubObjectFactory(); - final HookDefinition hook = mock(HookDefinition.class); - when(hook.getLocation()).thenReturn("hook-location"); - when(hook.getTagExpression()).thenReturn("("); - - doAnswer(invocation -> { - Glue glue = invocation.getArgument(0); - glue.addBeforeHook(hook); - return null; - }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); RuntimeException e = assertThrows(RuntimeException.class, () -> new Runner(bus, Collections.singleton(backend), objectFactory, @@ -111,4 +90,71 @@ public void stop() { } } + + private final static class StubBackend implements Backend { + private final HookDefinition beforeHook; + private final List eventListener; + + public StubBackend(HookDefinition beforeHook, List eventListener) { + this.beforeHook = beforeHook; + this.eventListener = eventListener; + } + + @Override + public void loadGlue(Glue glue, List gluePaths) { + glue.addBeforeHook(beforeHook); + } + + @Override + public void buildWorld() { + eventListener.add("buildWorld"); + } + + @Override + public void disposeWorld() { + eventListener.add("disposeWorld"); + } + + @Override + public Snippet getSnippet() { + return new TestSnippet(); + } + } + + private static final class MockHookDefinition implements HookDefinition { + private final String tagExpression; + private final String location; + private final List eventListener; + + public MockHookDefinition(String tagExpression, String location, List eventListener) { + this.tagExpression = tagExpression; + this.location = location; + this.eventListener = eventListener; + } + + @Override + public void execute(TestCaseState state) { + eventListener.add("execute"); + } + + @Override + public String getTagExpression() { + return tagExpression; + } + + @Override + public int getOrder() { + return 0; + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public String getLocation() { + return location; + } + } } diff --git a/cucumber-core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java b/cucumber-core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java index 60e561bde9..2dee028fc8 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/runner/PickleStepTestStepTest.java @@ -1,28 +1,31 @@ package io.cucumber.core.runner; +import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.backend.StubPendingException; import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.feature.TestFeatureParser; import io.cucumber.core.gherkin.Feature; import io.cucumber.core.gherkin.Pickle; +import io.cucumber.messages.types.Envelope; +import io.cucumber.plugin.event.EventHandler; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.TestCaseEvent; import io.cucumber.plugin.event.TestStepFinished; import io.cucumber.plugin.event.TestStepStarted; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatcher; -import org.mockito.InOrder; -import org.mockito.Mockito; import org.opentest4j.TestAbortedException; import java.net.URI; import java.time.Instant; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Queue; import java.util.UUID; +import java.util.stream.Collectors; import static io.cucumber.core.backend.Status.FAILED; import static io.cucumber.core.backend.Status.PASSED; @@ -30,7 +33,6 @@ import static io.cucumber.core.backend.Status.SKIPPED; import static io.cucumber.plugin.event.HookType.AFTER_STEP; import static io.cucumber.plugin.event.HookType.BEFORE_STEP; -import static java.time.Duration.ZERO; import static java.time.Duration.ofMillis; import static java.time.Instant.ofEpochMilli; import static java.util.Collections.singletonList; @@ -38,17 +40,10 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentCaptor.forClass; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; class PickleStepTestStepTest { @@ -59,207 +54,239 @@ class PickleStepTestStepTest { private final Pickle pickle = feature.getPickles().get(0); private final TestCase testCase = new TestCase(UUID.randomUUID(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), pickle, false); - private final EventBus bus = mock(EventBus.class); - private final UUID testExecutionId = UUID.randomUUID(); - private final TestCaseState state = new TestCaseState(bus, testExecutionId, testCase); - private final PickleStepDefinitionMatch definitionMatch = mock(PickleStepDefinitionMatch.class); - private final CoreHookDefinition afterHookDefinition = mock(CoreHookDefinition.class); - private final HookTestStep afterHook = new HookTestStep(UUID.randomUUID(), AFTER_STEP, - new HookDefinitionMatch(afterHookDefinition)); - private final CoreHookDefinition beforeHookDefinition = mock(CoreHookDefinition.class); - private final HookTestStep beforeHook = new HookTestStep(UUID.randomUUID(), BEFORE_STEP, - new HookDefinitionMatch(beforeHookDefinition)); - private final PickleStepTestStep step = new PickleStepTestStep( - UUID.randomUUID(), - URI.create("file:path/to.feature"), - pickle.getSteps().get(0), - singletonList(beforeHook), - singletonList(afterHook), - definitionMatch); - - @BeforeEach - void init() { - Mockito.when(bus.getInstant()).thenReturn(Instant.now()); + private MockEventBus bus = new MockEventBus(); + private final TestCaseState state = new TestCaseState(bus, UUID.randomUUID(), testCase); + private PickleStepDefinitionMatch definitionMatch; + private CoreHookDefinition afterHookDefinition; + private CoreHookDefinition beforeHookDefinition; + private PickleStepTestStep step; + + private void buildStep( + RuntimeException beforeHookException, RuntimeException afterHookException, Throwable stepException + ) { + beforeHookDefinition = CoreHookDefinition.create(new MockHookDefinition(beforeHookException)); + afterHookDefinition = CoreHookDefinition.create(new MockHookDefinition(afterHookException)); + definitionMatch = new MockPickleStepDefinitionMatch(stepException); + step = new PickleStepTestStep( + UUID.randomUUID(), + URI.create("file:path/to.feature"), + pickle.getSteps().get(0), + singletonList(new HookTestStep(UUID.randomUUID(), BEFORE_STEP, + new HookDefinitionMatch(beforeHookDefinition))), + singletonList(new HookTestStep(UUID.randomUUID(), AFTER_STEP, + new HookDefinitionMatch(afterHookDefinition))), + definitionMatch); } @Test void run_wraps_run_step_in_test_step_started_and_finished_events() throws Throwable { + buildStep(null, null, null); + step.run(testCase, bus, state, ExecutionMode.RUN); - InOrder order = inOrder(bus, definitionMatch); - order.verify(bus).send(isA(TestStepStarted.class)); - order.verify(definitionMatch).runStep(state); - order.verify(bus).send(isA(TestStepFinished.class)); + List events = bus.events.stream() + .filter(event -> !(event instanceof Envelope)) + .collect(Collectors.toList()); + assertInstanceOf(TestStepStarted.class, events.get(0)); + Object stepDefinitionEvent = events.get(3); + assertInstanceOf(PickleStepDefinitionMatchEvent.class, stepDefinitionEvent); + assertEquals("runStep", ((PickleStepDefinitionMatchEvent) stepDefinitionEvent).method); + assertEquals(state, ((PickleStepDefinitionMatchEvent) stepDefinitionEvent).state); + assertInstanceOf(TestStepFinished.class, events.get(events.size() - 1)); } @Test void run_does_dry_run_step_when_dry_run_steps_is_true() throws Throwable { - step.run(testCase, bus, state, ExecutionMode.DRY_RUN); - - InOrder order = inOrder(bus, definitionMatch); - order.verify(bus).send(isA(TestStepStarted.class)); - order.verify(definitionMatch).dryRunStep(state); - order.verify(bus).send(isA(TestStepFinished.class)); - } + buildStep(null, null, null); - @Test - void run_skips_step_when_dry_run_and_skip_step_is_true() throws Throwable { - step.run(testCase, bus, state, ExecutionMode.SKIP); + step.run(testCase, bus, state, ExecutionMode.DRY_RUN); - InOrder order = inOrder(bus, definitionMatch); - order.verify(bus).send(isA(TestStepStarted.class)); - order.verify(definitionMatch, never()).dryRunStep(state); - order.verify(bus).send(isA(TestStepFinished.class)); + List events = bus.events.stream() + .filter(event -> !(event instanceof Envelope)) + .collect(Collectors.toList()); + assertInstanceOf(TestStepStarted.class, events.get(0)); + Object stepDefinitionEvent = events.get(3); + assertInstanceOf(PickleStepDefinitionMatchEvent.class, stepDefinitionEvent); + assertEquals("dryRunStep", ((PickleStepDefinitionMatchEvent) stepDefinitionEvent).method); + assertEquals(state, ((PickleStepDefinitionMatchEvent) stepDefinitionEvent).state); + assertInstanceOf(TestStepFinished.class, events.get(events.size() - 1)); } @Test void run_skips_step_when_skip_step_is_true() throws Throwable { + buildStep(null, null, null); + step.run(testCase, bus, state, ExecutionMode.SKIP); - InOrder order = inOrder(bus, definitionMatch); - order.verify(bus).send(isA(TestStepStarted.class)); - order.verify(definitionMatch, never()).dryRunStep(state); - order.verify(bus).send(isA(TestStepFinished.class)); + List events = bus.events.stream() + .filter(event -> !(event instanceof Envelope)) + .collect(Collectors.toList()); + assertInstanceOf(TestStepStarted.class, events.get(0)); + assertFalse(bus.events.stream().anyMatch(event -> event instanceof PickleStepDefinitionMatchEvent)); + assertInstanceOf(TestStepFinished.class, events.get(events.size() - 1)); } @Test void result_is_passed_run_when_step_definition_does_not_throw_exception() { + buildStep(null, null, null); + ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.RUN); + assertThat(nextExecutionMode, is(ExecutionMode.RUN)); assertThat(state.getStatus(), is(equalTo(PASSED))); } @Test void result_is_skipped_when_skip_step_is_not_run_all() { + buildStep(null, null, null); + ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.SKIP); + assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); assertThat(state.getStatus(), is(equalTo(SKIPPED))); } @Test void result_is_skipped_when_before_step_hook_does_not_pass() { - doThrow(TestAbortedException.class).when(beforeHookDefinition).execute(any(TestCaseState.class)); + buildStep(new TestAbortedException(), null, null); + ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.RUN); + assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); assertThat(state.getStatus(), is(equalTo(SKIPPED))); } @Test void step_execution_is_skipped_when_before_step_hook_does_not_pass() throws Throwable { - doThrow(TestAbortedException.class).when(beforeHookDefinition).execute(any(TestCaseState.class)); + buildStep(new TestAbortedException(), null, null); + step.run(testCase, bus, state, ExecutionMode.RUN); - verify(definitionMatch, never()).runStep(any(TestCaseState.class)); - verify(definitionMatch, never()).dryRunStep(any(TestCaseState.class)); + + assertFalse(bus.events.stream().anyMatch(event -> event instanceof PickleStepDefinitionMatchEvent)); } @Test void result_is_result_from_hook_when_before_step_hook_does_not_pass() { - Exception exception = new RuntimeException(); - doThrow(exception).when(beforeHookDefinition).execute(any(TestCaseState.class)); - Result failure = new Result(Status.FAILED, ZERO, exception); + RuntimeException exception = new RuntimeException(); + buildStep(exception, null, null); + ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.RUN); + assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); assertThat(state.getStatus(), is(equalTo(FAILED))); - - ArgumentCaptor captor = forClass(TestCaseEvent.class); - verify(bus, times(6)).send(captor.capture()); - List allValues = captor.getAllValues(); - assertThat(((TestStepFinished) allValues.get(1)).getResult(), is(equalTo(failure))); + List events = bus.events.stream() + .filter(event -> event instanceof TestCaseEvent) + .map(event -> (TestCaseEvent) event) + .collect(Collectors.toList()); + assertEquals(6, events.size()); + Result result = ((TestStepFinished) events.get(1)).getResult(); + assertEquals(Status.FAILED, result.getStatus()); + assertEquals(exception, result.getError()); } @Test void result_is_result_from_step_when_step_hook_does_not_pass() throws Throwable { RuntimeException runtimeException = new RuntimeException(); - Result failure = new Result(Status.FAILED, ZERO, runtimeException); - doThrow(runtimeException).when(definitionMatch).runStep(any(TestCaseState.class)); + buildStep(null, null, runtimeException); + ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.RUN); + assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); assertThat(state.getStatus(), is(equalTo(FAILED))); - - ArgumentCaptor captor = forClass(TestCaseEvent.class); - verify(bus, times(6)).send(captor.capture()); - List allValues = captor.getAllValues(); - assertThat(((TestStepFinished) allValues.get(3)).getResult(), is(equalTo(failure))); + List events = bus.events.stream() + .filter(event -> event instanceof TestCaseEvent) + .map(event -> (TestCaseEvent) event) + .collect(Collectors.toList()); + assertEquals(6, events.size()); + Result result = ((TestStepFinished) events.get(3)).getResult(); + assertEquals(Status.FAILED, result.getStatus()); + assertEquals(runtimeException, result.getError()); } @Test void result_is_result_from_hook_when_after_step_hook_does_not_pass() { - Exception exception = new RuntimeException(); - Result failure = new Result(Status.FAILED, ZERO, exception); - doThrow(exception).when(afterHookDefinition).execute(any(TestCaseState.class)); + RuntimeException exception = new RuntimeException(); + buildStep(null, exception, null); + ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.RUN); + assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); assertThat(state.getStatus(), is(equalTo(FAILED))); - - ArgumentCaptor captor = forClass(TestCaseEvent.class); - verify(bus, times(6)).send(captor.capture()); - List allValues = captor.getAllValues(); - assertThat(((TestStepFinished) allValues.get(5)).getResult(), is(equalTo(failure))); + List events = bus.events.stream() + .filter(event -> event instanceof TestCaseEvent) + .map(event -> (TestCaseEvent) event) + .collect(Collectors.toList()); + assertEquals(6, events.size()); + Result result = ((TestStepFinished) events.get(5)).getResult(); + assertEquals(Status.FAILED, result.getStatus()); + assertEquals(exception, result.getError()); } @Test void after_step_hook_is_run_when_before_step_hook_does_not_pass() { - doThrow(RuntimeException.class).when(beforeHookDefinition).execute(any(TestCaseState.class)); + buildStep(new RuntimeException(), null, null); + step.run(testCase, bus, state, ExecutionMode.RUN); - verify(afterHookDefinition).execute(any(TestCaseState.class)); + + assertTrue(((MockHookDefinition) afterHookDefinition.delegate).executed); } @Test void after_step_hook_is_run_when_step_does_not_pass() throws Throwable { - doThrow(Exception.class).when(definitionMatch).runStep(any(TestCaseState.class)); + buildStep(null, null, new Exception()); + step.run(testCase, bus, state, ExecutionMode.RUN); - verify(afterHookDefinition).execute(any(TestCaseState.class)); + + assertTrue(((MockHookDefinition) afterHookDefinition.delegate).executed); } @Test void after_step_hook_scenario_contains_step_failure_when_step_does_not_pass() throws Throwable { Throwable expectedError = new TestAbortedException("oops"); - doThrow(expectedError).when(definitionMatch).runStep(any(TestCaseState.class)); - doThrow(new RuntimeException()).when(afterHookDefinition).execute(argThat(scenarioDoesNotHave(expectedError))); + buildStep(null, null, expectedError); + step.run(testCase, bus, state, ExecutionMode.RUN); - assertThat(state.getError(), is(expectedError)); - } - private static ArgumentMatcher scenarioDoesNotHave(final Throwable type) { - return argument -> !type.equals(argument.getError()); + assertThat(((MockHookDefinition) afterHookDefinition.delegate).receivedError, is(expectedError)); } @Test void after_step_hook_scenario_contains_before_step_hook_failure_when_before_step_hook_does_not_pass() { - Throwable expectedError = new TestAbortedException("oops"); - doThrow(expectedError).when(beforeHookDefinition).execute(any(TestCaseState.class)); - doThrow(new RuntimeException()).when(afterHookDefinition).execute(argThat(scenarioDoesNotHave(expectedError))); + RuntimeException expectedError = new TestAbortedException("oops"); + buildStep(expectedError, null, null); + step.run(testCase, bus, state, ExecutionMode.RUN); - assertThat(state.getError(), is(expectedError)); + + assertThat(((MockHookDefinition) afterHookDefinition.delegate).receivedError, is(expectedError)); } @Test void result_is_skipped_when_step_definition_throws_assumption_violated_exception() throws Throwable { - doThrow(TestAbortedException.class).when(definitionMatch).runStep(any()); + buildStep(null, null, new TestAbortedException()); ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.RUN); - assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); + assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); assertThat(state.getStatus(), is(equalTo(SKIPPED))); } @Test void result_is_failed_when_step_definition_throws_exception() throws Throwable { - doThrow(RuntimeException.class).when(definitionMatch).runStep(any(TestCaseState.class)); + buildStep(null, null, new RuntimeException()); ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.RUN); - assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); + assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); assertThat(state.getStatus(), is(equalTo(FAILED))); } @Test void result_is_pending_when_step_definition_throws_pending_exception() throws Throwable { - doThrow(StubPendingException.class).when(definitionMatch).runStep(any(TestCaseState.class)); + buildStep(null, null, new StubPendingException()); ExecutionMode nextExecutionMode = step.run(testCase, bus, state, ExecutionMode.RUN); - assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); + assertThat(nextExecutionMode, is(ExecutionMode.SKIP)); assertThat(state.getStatus(), is(equalTo(PENDING))); } @@ -269,26 +296,142 @@ void step_execution_time_is_measured() { "Feature: Test feature\n" + " Scenario: Test scenario\n" + " Given I have 4 cukes in my belly\n"); - TestStep step = new PickleStepTestStep( UUID.randomUUID(), URI.create("file:path/to.feature"), feature.getPickles().get(0).getSteps().get(0), definitionMatch); - when(bus.getInstant()).thenReturn(ofEpochMilli(234L), ofEpochMilli(1234L)); - step.run(testCase, bus, state, ExecutionMode.RUN); - - ArgumentCaptor captor = forClass(TestCaseEvent.class); - verify(bus, times(2)).send(captor.capture()); + bus = new MockEventBus(ofEpochMilli(234L), ofEpochMilli(1234L)); - List allValues = captor.getAllValues(); - TestStepStarted started = (TestStepStarted) allValues.get(0); - TestStepFinished finished = (TestStepFinished) allValues.get(1); + step.run(testCase, bus, state, ExecutionMode.RUN); + List events = bus.events.stream() + .filter(event -> event instanceof TestCaseEvent) + .map(event -> (TestCaseEvent) event) + .collect(Collectors.toList()); + assertEquals(2, events.size()); + TestStepStarted started = (TestStepStarted) events.get(0); + TestStepFinished finished = (TestStepFinished) events.get(1); assertAll( () -> assertThat(started.getInstant(), is(equalTo(ofEpochMilli(234L)))), () -> assertThat(finished.getInstant(), is(equalTo(ofEpochMilli(1234L)))), () -> assertThat(finished.getResult().getDuration(), is(equalTo(ofMillis(1000L))))); } + private static class MockEventBus implements EventBus { + final List events = new ArrayList<>(); + final Queue instants; + + public MockEventBus(Instant... instants) { + this.instants = new ArrayDeque<>(Arrays.asList(instants)); + } + + @Override + public Instant getInstant() { + Instant instant = instants.poll(); + return instant != null ? instant : Instant.now(); + } + + @Override + public UUID generateId() { + return null; + } + + @Override + public void send(T event) { + events.add(event); + } + + @Override + public void sendAll(Iterable queue) { + queue.forEach(events::add); + } + + @Override + public void registerHandlerFor(Class eventType, EventHandler handler) { + + } + + @Override + public void removeHandlerFor(Class eventType, EventHandler handler) { + + } + } + + private static class PickleStepDefinitionMatchEvent { + Object target; + String method; + io.cucumber.core.backend.TestCaseState state; + public PickleStepDefinitionMatchEvent( + Object target, String method, io.cucumber.core.backend.TestCaseState state + ) { + this.target = target; + this.method = method; + this.state = state; + } + } + + private class MockPickleStepDefinitionMatch extends PickleStepDefinitionMatch { + private final Throwable stepException; + + MockPickleStepDefinitionMatch(Throwable stepException) { + super(new ArrayList<>(), new StubStepDefinition(""), null, null); + this.stepException = stepException; + } + + @Override + public void runStep(io.cucumber.core.backend.TestCaseState state) throws Throwable { + bus.send(new PickleStepDefinitionMatchEvent(this, "runStep", state)); + if (stepException != null) { + throw stepException; + } + } + + @Override + public void dryRunStep(io.cucumber.core.backend.TestCaseState state) throws Throwable { + bus.send(new PickleStepDefinitionMatchEvent(this, "dryRunStep", state)); + if (stepException != null) { + throw stepException; + } + } + } + + private static class MockHookDefinition implements HookDefinition { + private final RuntimeException executeException; + boolean executed; + Throwable receivedError; + + public MockHookDefinition(RuntimeException executeException) { + this.executeException = executeException; + } + + @Override + public void execute(io.cucumber.core.backend.TestCaseState state) { + executed = true; + receivedError = ((TestCaseState) state).getError(); + if (executeException != null) { + throw executeException; + } + } + + @Override + public String getTagExpression() { + return ""; + } + + @Override + public int getOrder() { + return 0; + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public String getLocation() { + return null; + } + } } diff --git a/cucumber-core/src/test/java/io/cucumber/core/runner/RunnerTest.java b/cucumber-core/src/test/java/io/cucumber/core/runner/RunnerTest.java index 33b555db97..264e77a8bd 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/runner/RunnerTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/runner/RunnerTest.java @@ -4,6 +4,7 @@ import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.HookDefinition; import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.backend.Snippet; import io.cucumber.core.backend.StaticHookDefinition; import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.feature.TestFeatureParser; @@ -14,11 +15,11 @@ import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.core.snippets.TestSnippet; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.InOrder; import java.net.URI; import java.time.Clock; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -28,53 +29,33 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.assertTrue; class RunnerTest { private final RuntimeOptions runtimeOptions = RuntimeOptions.defaultOptions(); private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); - @Test void hooks_execute_inside_world_and_around_world() { - StaticHookDefinition beforeAllHook = createStaticHook(); - StaticHookDefinition afterAllHook = createStaticHook(); - HookDefinition beforeHook = createHook(); - HookDefinition afterHook = createHook(); - - Backend backend = mock(Backend.class); - when(backend.getSnippet()).thenReturn(new TestSnippet()); - ObjectFactory objectFactory = mock(ObjectFactory.class); - doAnswer(invocation -> { - Glue glue = invocation.getArgument(0); - glue.addBeforeAllHook(beforeAllHook); - glue.addAfterAllHook(afterAllHook); - glue.addBeforeHook(beforeHook); - glue.addAfterHook(afterHook); - return null; - }).when(backend).loadGlue(any(Glue.class), ArgumentMatchers.anyList()); + List listener = new ArrayList<>(); + StaticHookDefinition beforeAllHook = new MockStaticHookDefinition("beforeAllHook", listener); + StaticHookDefinition afterAllHook = new MockStaticHookDefinition("afterAllHook", listener); + HookDefinition beforeHook = new MockHookDefinition("beforeHook", listener); + HookDefinition afterHook = new MockHookDefinition("afterHook", listener); + + Backend backend = new MockBackend(beforeAllHook, afterAllHook, beforeHook, afterHook, listener); + ObjectFactory objectFactory = new MockObjectFactory(); Runner runner = new Runner(bus, singletonList(backend), objectFactory, runtimeOptions); runner.runBeforeAllHooks(); runner.runPickle(createPicklesWithSteps()); runner.runAfterAllHooks(); - InOrder inOrder = inOrder(beforeAllHook, afterAllHook, beforeHook, afterHook, backend); - inOrder.verify(beforeAllHook).execute(); - inOrder.verify(backend).buildWorld(); - inOrder.verify(beforeHook).execute(any(TestCaseState.class)); - inOrder.verify(afterHook).execute(any(TestCaseState.class)); - inOrder.verify(backend).disposeWorld(); - inOrder.verify(afterAllHook).execute(); + assertLinesMatch( + List.of("beforeAllHook", "buildWorld", "beforeHook", "afterHook", "disposeWorld", "afterAllHook"), + listener); } private Pickle createPicklesWithSteps() { @@ -85,26 +66,14 @@ private Pickle createPicklesWithSteps() { return feature.getPickles().get(0); } - private StaticHookDefinition createStaticHook() { - StaticHookDefinition hook = mock(StaticHookDefinition.class); - when(hook.getLocation()).thenReturn(""); - return hook; - } - - private HookDefinition createHook() { - HookDefinition hook = mock(HookDefinition.class); - when(hook.getTagExpression()).thenReturn(""); - when(hook.getLocation()).thenReturn(""); - return hook; - } - @Test void steps_are_skipped_after_failure() { - StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step")); + List listener = new ArrayList<>(); + StubStepDefinition stepDefinition = new MockStubStepDefinition(listener); Pickle pickleMatchingStepDefinitions = createPickleMatchingStepDefinitions(stepDefinition); - final HookDefinition failingBeforeHook = createHook(); - doThrow(new RuntimeException("Boom")).when(failingBeforeHook).execute(ArgumentMatchers.any()); + final HookDefinition failingBeforeHook = new MockHookDefinition("beforeHook", listener, + new RuntimeException("Boom")); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override public void loadGlue(Glue glue, List gluePaths) { @@ -115,9 +84,7 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleMatchingStepDefinitions); - InOrder inOrder = inOrder(failingBeforeHook, stepDefinition); - inOrder.verify(failingBeforeHook).execute(any(TestCaseState.class)); - inOrder.verify(stepDefinition, never()).execute(any(Object[].class)); + assertLinesMatch(List.of("beforeHook"), listener); } private Pickle createPickleMatchingStepDefinitions(StubStepDefinition stepDefinition) { @@ -131,18 +98,11 @@ private Pickle createPickleMatchingStepDefinitions(StubStepDefinition stepDefini @Test void aftersteps_are_executed_after_failed_step() { - StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step") { - - @Override - public void execute(Object[] args) { - super.execute(args); - throw new RuntimeException(); - } - }); - + List listener = new ArrayList<>(); + StubStepDefinition stepDefinition = new MockStubStepDefinition(listener, new RuntimeException()); Pickle pickleMatchingStepDefinitions = createPickleMatchingStepDefinitions(stepDefinition); - final HookDefinition afterStepHook = createHook(); + final HookDefinition afterStepHook = new MockHookDefinition("afterHook", listener); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override @@ -154,18 +114,17 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickleMatchingStepDefinitions); - InOrder inOrder = inOrder(afterStepHook, stepDefinition); - inOrder.verify(stepDefinition).execute(any(Object[].class)); - inOrder.verify(afterStepHook).execute(any(TestCaseState.class)); + assertLinesMatch(List.of("stepDefinition", "afterHook"), listener); } @Test void aftersteps_executed_for_passed_step() { - StubStepDefinition stepDefinition = spy(new StubStepDefinition("some step")); + List listener = new ArrayList<>(); + StubStepDefinition stepDefinition = new MockStubStepDefinition(listener); Pickle pickle = createPickleMatchingStepDefinitions(stepDefinition); - HookDefinition afteStepHook1 = createHook(); - HookDefinition afteStepHook2 = createHook(); + HookDefinition afteStepHook1 = new MockHookDefinition("afterHook1", listener); + HookDefinition afteStepHook2 = new MockHookDefinition("afterHook2", listener); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override @@ -178,19 +137,18 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(pickle); - InOrder inOrder = inOrder(afteStepHook1, afteStepHook2, stepDefinition); - inOrder.verify(stepDefinition).execute(any(Object[].class)); - inOrder.verify(afteStepHook2).execute(any(TestCaseState.class)); - inOrder.verify(afteStepHook1).execute(any(TestCaseState.class)); + assertLinesMatch(List.of("stepDefinition", "afterHook2", "afterHook1"), listener); } @Test void hooks_execute_also_after_failure() { - HookDefinition beforeHook = createHook(); - HookDefinition afterHook = createHook(); + List listener = new ArrayList<>(); + HookDefinition beforeHook = new MockHookDefinition("beforeHook", listener); + HookDefinition afterHook = new MockHookDefinition("afterHook", listener); + ; - HookDefinition failingBeforeHook = createHook(); - doThrow(new RuntimeException("boom")).when(failingBeforeHook).execute(any(TestCaseState.class)); + HookDefinition failingBeforeHook = new MockHookDefinition("failingBeforeHook", listener, + new RuntimeException("boom")); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override @@ -203,17 +161,15 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(createPicklesWithSteps()); - InOrder inOrder = inOrder(failingBeforeHook, beforeHook, afterHook); - inOrder.verify(failingBeforeHook).execute(any(TestCaseState.class)); - inOrder.verify(beforeHook).execute(any(TestCaseState.class)); - inOrder.verify(afterHook).execute(any(TestCaseState.class)); + assertLinesMatch(List.of("failingBeforeHook", "beforeHook", "afterHook"), listener); } @Test void all_static_hooks_execute_also_after_failure() { - StaticHookDefinition beforeAllHook = createStaticHook(); - StaticHookDefinition failingBeforeAllHook = createStaticHook(); - doThrow(new RuntimeException("boom")).when(failingBeforeAllHook).execute(); + List listener = new ArrayList<>(); + StaticHookDefinition beforeAllHook = new MockStaticHookDefinition("beforeAllHook", listener); + StaticHookDefinition failingBeforeAllHook = new MockStaticHookDefinition("failingBeforeAllHook", listener, + new RuntimeException("boom")); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @Override @@ -226,9 +182,7 @@ public void loadGlue(Glue glue, List gluePaths) { Runner runner = runnerSupplier.get(); assertThrows(RuntimeException.class, runner::runBeforeAllHooks); - InOrder inOrder = inOrder(beforeAllHook, failingBeforeAllHook); - inOrder.verify(beforeAllHook).execute(); - inOrder.verify(failingBeforeAllHook).execute(); + assertLinesMatch(List.of("beforeAllHook", "failingBeforeAllHook"), listener); } @Test @@ -265,12 +219,13 @@ public void loadGlue(Glue glue, List gluePaths) { void hooks_not_executed_in_dry_run_mode() { RuntimeOptions runtimeOptions = new RuntimeOptionsBuilder().setDryRun().build(); - StaticHookDefinition beforeAllHook = createStaticHook(); - StaticHookDefinition afterAllHook = createStaticHook(); - HookDefinition beforeHook = createHook(); - HookDefinition afterHook = createHook(); - HookDefinition beforeStepHook = createHook(); - HookDefinition afterStepHook = createHook(); + List listener = new ArrayList<>(); + StaticHookDefinition beforeAllHook = new MockStaticHookDefinition("beforeAllHook", listener); + StaticHookDefinition afterAllHook = new MockStaticHookDefinition("afterAllHook", listener); + HookDefinition beforeHook = new MockHookDefinition("beforeHook", listener); + HookDefinition afterHook = new MockHookDefinition("afterHook", listener); + HookDefinition beforeStepHook = new MockHookDefinition("beforeStepHook", listener); + HookDefinition afterStepHook = new MockHookDefinition("afterStepHook", listener); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @@ -288,20 +243,16 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(createPicklesWithSteps()); runnerSupplier.get().runAfterAllHooks(); - verify(beforeAllHook, never()).execute(); - verify(afterAllHook, never()).execute(); - verify(beforeHook, never()).execute(any(TestCaseState.class)); - verify(afterHook, never()).execute(any(TestCaseState.class)); - verify(beforeStepHook, never()).execute(any(TestCaseState.class)); - verify(afterStepHook, never()).execute(any(TestCaseState.class)); + assertLinesMatch(Collections.emptyList(), listener); } @Test void scenario_hooks_not_executed_for_empty_pickles() { - HookDefinition beforeHook = createHook(); - HookDefinition afterHook = createHook(); - HookDefinition beforeStepHook = createHook(); - HookDefinition afterStepHook = createHook(); + List listener = new ArrayList<>(); + HookDefinition beforeHook = new MockHookDefinition("beforeHook", listener); + HookDefinition afterHook = new MockHookDefinition("afterHook", listener); + HookDefinition beforeStepHook = new MockHookDefinition("beforeStepHook", listener); + HookDefinition afterStepHook = new MockHookDefinition("afterStepHook", listener); TestRunnerSupplier runnerSupplier = new TestRunnerSupplier(bus, runtimeOptions) { @@ -316,9 +267,7 @@ public void loadGlue(Glue glue, List gluePaths) { runnerSupplier.get().runPickle(createEmptyPickle()); - verify(beforeHook, never()).execute(any(TestCaseState.class)); - verify(afterStepHook, never()).execute(any(TestCaseState.class)); - verify(afterHook, never()).execute(any(TestCaseState.class)); + assertLinesMatch(Collections.emptyList(), listener); } private Pickle createEmptyPickle() { @@ -330,12 +279,192 @@ private Pickle createEmptyPickle() { @Test void backends_are_asked_for_snippets_for_undefined_steps() { - Backend backend = mock(Backend.class); - when(backend.getSnippet()).thenReturn(new TestSnippet()); - ObjectFactory objectFactory = mock(ObjectFactory.class); + List listener = new ArrayList<>(); + MockBackend backend = new MockBackend(null, null, null, null, listener); + ObjectFactory objectFactory = new MockObjectFactory(); Runner runner = new Runner(bus, singletonList(backend), objectFactory, runtimeOptions); runner.runPickle(createPicklesWithSteps()); - verify(backend).getSnippet(); + assertTrue(backend.getSnippetCalled); + } + + private static class MockBackend implements Backend { + private final StaticHookDefinition beforeAllHook; + private final StaticHookDefinition afterAllHook; + private final HookDefinition beforeHook; + private final HookDefinition afterHook; + private final List listener; + boolean getSnippetCalled; + + public MockBackend( + StaticHookDefinition beforeAllHook, StaticHookDefinition afterAllHook, + HookDefinition beforeHook, HookDefinition afterHook, List listener + ) { + this.beforeAllHook = beforeAllHook; + this.afterAllHook = afterAllHook; + this.beforeHook = beforeHook; + this.afterHook = afterHook; + this.listener = listener; + } + + @Override + public void loadGlue(Glue glue, List gluePaths) { + if (beforeAllHook != null) { + glue.addBeforeAllHook(beforeAllHook); + } + if (afterAllHook != null) { + glue.addAfterAllHook(afterAllHook); + } + if (beforeHook != null) { + glue.addBeforeHook(beforeHook); + } + if (afterHook != null) { + glue.addAfterHook(afterHook); + } + } + + @Override + public void buildWorld() { + listener.add("buildWorld"); + } + + @Override + public void disposeWorld() { + listener.add("disposeWorld"); + } + + @Override + public Snippet getSnippet() { + getSnippetCalled = true; + return new TestSnippet(); + } + } + + private static class MockObjectFactory implements ObjectFactory { + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } } + private static class MockStaticHookDefinition implements StaticHookDefinition { + private final String hookName; + private final List listener; + private final RuntimeException exception; + + public MockStaticHookDefinition(String hookName, List listener) { + this(hookName, listener, null); + } + + public MockStaticHookDefinition(String hookName, List listener, RuntimeException exception) { + this.hookName = hookName; + this.listener = listener; + this.exception = exception; + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public String getLocation() { + return ""; + } + + @Override + public void execute() { + listener.add(hookName); + if (exception != null) { + throw exception; + } + } + + @Override + public int getOrder() { + return 0; + } + } + + private static class MockHookDefinition implements HookDefinition { + private final String hookName; + private final List listener; + private final RuntimeException exception; + + public MockHookDefinition(String hookName, List listener) { + this(hookName, listener, null); + } + + public MockHookDefinition(String hookName, List listener, RuntimeException exception) { + this.hookName = hookName; + this.listener = listener; + this.exception = exception; + } + + @Override + public void execute(io.cucumber.core.backend.TestCaseState state) { + listener.add(hookName); + if (exception != null) { + throw exception; + } + } + + @Override + public String getTagExpression() { + return ""; + } + + @Override + public int getOrder() { + return 0; + } + + @Override + public boolean isDefinedAt(StackTraceElement stackTraceElement) { + return false; + } + + @Override + public String getLocation() { + return ""; + } + } + + private static class MockStubStepDefinition extends StubStepDefinition { + private final List listener; + private final RuntimeException exception; + + MockStubStepDefinition(List listener) { + this(listener, null); + } + + MockStubStepDefinition(List listener, RuntimeException exception) { + super("some step"); + this.listener = listener; + this.exception = exception; + } + + @Override + public void execute(Object[] args) { + super.execute(args); + listener.add("stepDefinition"); + if (exception != null) { + throw exception; + } + } + } }