diff --git a/core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java b/core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index 92d00304cf..c9acbeab02 100644 --- a/core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java +++ b/core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -37,7 +37,7 @@ public void setEventPublisher(EventPublisher publisher) { } private void handleSnippetsSuggestedEvent(SnippetsSuggestedEvent event) { - this.snippets.addAll(event.getSnippets()); + this.snippets.addAll(event.getSuggestion().getSnippets()); } private void print() { diff --git a/core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java b/core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java index aae7cd0344..1af941d9a7 100644 --- a/core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java +++ b/core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java @@ -11,6 +11,7 @@ import io.cucumber.plugin.event.PickleStepTestStep; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.TestCase; import io.cucumber.plugin.event.TestCaseFinished; @@ -42,8 +43,10 @@ import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.joining; public class TeamCityPlugin implements EventListener { @@ -86,9 +89,10 @@ public class TeamCityPlugin implements EventListener { private static final Pattern LAMBDA_GLUE_CODE_LOCATION_PATTERN = Pattern.compile("^(.*)\\.(.*)\\(.*:.*\\)"); private final PrintStream out; - private final List snippets = new ArrayList<>(); + private final List suggestions = new ArrayList<>(); private final Map> parsedTestSources = new HashMap<>(); private List currentStack = new ArrayList<>(); + private TestCase currentTestCase; @SuppressWarnings("unused") // Used by PluginFactory public TeamCityPlugin() { @@ -150,6 +154,7 @@ private void printTestCaseStarted(TestCaseStarted event) { poppedNodes(path).forEach(node -> finishNode(timestamp, node)); pushedNodes(path).forEach(node -> startNode(uri, timestamp, node)); this.currentStack = path; + this.currentTestCase = testCase; print(TEMPLATE_PROGRESS_TEST_STARTED, timestamp); } @@ -254,8 +259,7 @@ private void printTestStepFinished(TestStepFinished event) { name); break; case UNDEFINED: - PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep(); - print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step undefined", getSnippet(testStep), name); + print(TEMPLATE_TEST_FAILED, timeStamp, duration, "Step undefined", getSnippets(currentTestCase), name); break; case AMBIGUOUS: case FAILED: @@ -299,31 +303,41 @@ private String extractName(TestStep step) { return "Unknown step"; } - private String getSnippet(PickleStepTestStep testStep) { - StringBuilder builder = new StringBuilder(); + private String getSnippets(TestCase testCase) { + URI uri = testCase.getUri(); + Location location = testCase.getLocation(); + List suggestionForTestCase = suggestions.stream() + .filter(suggestions -> suggestions.getUri().equals(uri) && + suggestions.getTestCaseLocation().equals(location)) + .map(SnippetsSuggestedEvent::getSuggestion) + .collect(Collectors.toList()); + return createMessage(suggestionForTestCase); + } - if (snippets.isEmpty()) { - return builder.toString(); + private static String createMessage(Collection suggestions) { + if (suggestions.isEmpty()) { + return ""; } - - snippets.stream() - .filter(snippet -> snippet.getStepLocation().equals(testStep.getStep().getLocation()) && - snippet.getUri().equals(testStep.getUri())) - .findFirst() - .ifPresent(event -> { - builder.append("You can implement missing steps with the snippets below:\n"); - event.getSnippets().forEach(snippet -> { - builder.append(snippet); - builder.append("\n"); - }); - }); - return builder.toString(); + StringBuilder sb = new StringBuilder("You can implement this step"); + if (suggestions.size() > 1) { + sb.append(" and ").append(suggestions.size() - 1).append(" other step(s)"); + } + sb.append("using the snippet(s) below:\n\n"); + String snippets = suggestions + .stream() + .map(Suggestion::getSnippets) + .flatMap(Collection::stream) + .distinct() + .collect(joining("\n", "", "\n")); + sb.append(snippets); + return sb.toString(); } private void printTestCaseFinished(TestCaseFinished event) { String timestamp = extractTimeStamp(event); print(TEMPLATE_PROGRESS_TEST_FINISHED, timestamp); finishNode(timestamp, currentStack.remove(currentStack.size() - 1)); + this.currentTestCase = null; } private long extractDuration(Result result) { @@ -342,7 +356,7 @@ private void printTestRunFinished(TestRunFinished event) { } private void handleSnippetSuggested(SnippetsSuggestedEvent event) { - snippets.add(event); + suggestions.add(event); } private void handleEmbedEvent(EmbedEvent event) { diff --git a/core/src/main/java/io/cucumber/core/runner/Runner.java b/core/src/main/java/io/cucumber/core/runner/Runner.java index 635034d48d..83065ac21a 100644 --- a/core/src/main/java/io/cucumber/core/runner/Runner.java +++ b/core/src/main/java/io/cucumber/core/runner/Runner.java @@ -11,7 +11,9 @@ import io.cucumber.core.snippets.SnippetGenerator; import io.cucumber.core.stepexpression.StepTypeRegistry; import io.cucumber.plugin.event.HookType; +import io.cucumber.plugin.event.Location; import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import java.net.URI; import java.util.ArrayList; @@ -147,17 +149,26 @@ private PickleStepDefinitionMatch matchStepToStepDefinition(Pickle pickle, Step if (match != null) { return match; } - List snippets = generateSnippetsForStep(step); - if (!snippets.isEmpty()) { - bus.send(new SnippetsSuggestedEvent(bus.getInstant(), pickle.getUri(), pickle.getScenarioLocation(), - step.getLocation(), snippets)); - } + emitSnippetSuggestedEvent(pickle, step); return new UndefinedPickleStepDefinitionMatch(pickle.getUri(), step); } catch (AmbiguousStepDefinitionsException e) { return new AmbiguousPickleStepDefinitionsMatch(pickle.getUri(), step, e); } } + private void emitSnippetSuggestedEvent(Pickle pickle, Step step) { + List snippets = generateSnippetsForStep(step); + if (snippets.isEmpty()) { + return; + } + Suggestion suggestion = new Suggestion(step.getText(), snippets); + Location scenarioLocation = pickle.getLocation(); + Location stepLocation = step.getLocation(); + SnippetsSuggestedEvent event = new SnippetsSuggestedEvent(bus.getInstant(), pickle.getUri(), scenarioLocation, + stepLocation, suggestion); + bus.send(event); + } + private List createAfterStepHooks(List tags) { return createTestStepsForHooks(tags, glue.getAfterStepHooks(), HookType.AFTER_STEP); } diff --git a/core/src/main/java/io/cucumber/core/runtime/TestCaseResultObserver.java b/core/src/main/java/io/cucumber/core/runtime/TestCaseResultObserver.java index 55430a1717..2fbf7c053e 100644 --- a/core/src/main/java/io/cucumber/core/runtime/TestCaseResultObserver.java +++ b/core/src/main/java/io/cucumber/core/runtime/TestCaseResultObserver.java @@ -2,19 +2,13 @@ import io.cucumber.plugin.event.EventHandler; import io.cucumber.plugin.event.EventPublisher; -import io.cucumber.plugin.event.PickleStepTestStep; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.SnippetsSuggestedEvent; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.TestCaseFinished; -import io.cucumber.plugin.event.TestStep; -import io.cucumber.plugin.event.TestStepFinished; -import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import java.util.function.Function; import java.util.function.Supplier; @@ -22,59 +16,32 @@ import static io.cucumber.plugin.event.Status.PENDING; import static io.cucumber.plugin.event.Status.SKIPPED; import static io.cucumber.plugin.event.Status.UNDEFINED; +import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; public final class TestCaseResultObserver implements AutoCloseable { private final EventPublisher bus; - private final Map> snippetsPerStep = new TreeMap<>(); private final List suggestions = new ArrayList<>(); private final EventHandler snippetsSuggested = this::handleSnippetSuggestedEvent; - private final EventHandler testStepFinished = this::handleTestStepFinished; private Result result; private final EventHandler testCaseFinished = this::handleTestCaseFinished; public TestCaseResultObserver(EventPublisher bus) { this.bus = bus; bus.registerHandlerFor(SnippetsSuggestedEvent.class, snippetsSuggested); - bus.registerHandlerFor(TestStepFinished.class, testStepFinished); bus.registerHandlerFor(TestCaseFinished.class, testCaseFinished); } @Override public void close() { bus.removeHandlerFor(SnippetsSuggestedEvent.class, snippetsSuggested); - bus.removeHandlerFor(TestStepFinished.class, testStepFinished); bus.removeHandlerFor(TestCaseFinished.class, testCaseFinished); } private void handleSnippetSuggestedEvent(SnippetsSuggestedEvent event) { - snippetsPerStep.putIfAbsent(new StepLocation( - event.getUri(), - event.getStepLine()), - event.getSnippets()); - } - - private void handleTestStepFinished(TestStepFinished event) { - Result result = event.getResult(); - Status status = result.getStatus(); - if (!status.is(UNDEFINED)) { - return; - } - - TestStep testStep = event.getTestStep(); - if (!(testStep instanceof PickleStepTestStep)) { - return; - } - - PickleStepTestStep pickleStepTestStep = (PickleStepTestStep) testStep; - String stepText = pickleStepTestStep.getStepText(); - - List snippets = snippetsPerStep.get( - new StepLocation( - pickleStepTestStep.getUri(), - pickleStepTestStep.getStepLine())); - suggestions.add(new Suggestion(stepText, snippets)); + SnippetsSuggestedEvent.Suggestion s = event.getSuggestion(); + suggestions.add(new Suggestion(s.getStep(), s.getSnippets())); } private void handleTestCaseFinished(TestCaseFinished event) { @@ -117,32 +84,14 @@ public TestCaseFailed(Throwable throwable) { } - private static final class StepLocation implements Comparable { - - private final URI uri; - private final int line; - - private StepLocation(URI uri, int line) { - this.uri = uri; - this.line = line; - } - - @Override - public int compareTo(StepLocation o) { - int order = uri.compareTo(o.uri); - return order != 0 ? order : Integer.compare(line, o.line); - } - - } - public static final class Suggestion { final String step; final List snippets; public Suggestion(String step, List snippets) { - this.step = step; - this.snippets = snippets; + this.step = requireNonNull(step); + this.snippets = unmodifiableList(requireNonNull(snippets)); } public String getStep() { diff --git a/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java b/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java index 3c9b03aabe..da413f7885 100644 --- a/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/CanonicalEventOrderTest.java @@ -4,6 +4,7 @@ import io.cucumber.plugin.event.Location; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.TestCase; import io.cucumber.plugin.event.TestCaseStarted; @@ -48,13 +49,13 @@ class CanonicalEventOrderTest { URI.create("file:path/to/1.feature"), new Location(0, -1), new Location(0, -1), - Collections.emptyList()); + new Suggestion("", Collections.emptyList())); private final Event suggested2 = new SnippetsSuggestedEvent( ofEpochMilli(5), URI.create("file:path/to/1.feature"), new Location(0, -1), new Location(0, -1), - Collections.emptyList()); + new Suggestion("", Collections.emptyList())); private final Event feature1Case1Started = createTestCaseEvent( ofEpochMilli(5), URI.create("file:path/to/1.feature"), diff --git a/core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java b/core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java index 73466eb999..01d7ccaca3 100644 --- a/core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java @@ -5,6 +5,7 @@ import io.cucumber.plugin.event.Location; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.TestRunFinished; import org.junit.jupiter.api.BeforeEach; @@ -44,14 +45,14 @@ void does_not_print_duplicate_snippets() { URI.create("classpath:com/example.feature"), new Location(12, -1), new Location(13, -1), - singletonList("snippet"))); + new Suggestion("", singletonList("snippet")))); bus.send(new SnippetsSuggestedEvent( bus.getInstant(), URI.create("classpath:com/example.feature"), new Location(12, -1), new Location(14, -1), - singletonList("snippet"))); + new Suggestion("", singletonList("snippet")))); bus.send(new TestRunFinished(bus.getInstant(), new Result(Status.PASSED, Duration.ZERO, null))); diff --git a/core/src/test/java/io/cucumber/core/plugin/TeamCityPluginTest.java b/core/src/test/java/io/cucumber/core/plugin/TeamCityPluginTest.java index e01cf056d9..04c1c47bc2 100755 --- a/core/src/test/java/io/cucumber/core/plugin/TeamCityPluginTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/TeamCityPluginTest.java @@ -199,7 +199,8 @@ void should_print_error_message_for_undefined_steps() { Feature feature = TestFeatureParser.parse("path/test.feature", "" + "Feature: feature name\n" + " Scenario: scenario name\n" + - " Given first step\n"); + " Given first step\n" + + " Given second step\n"); ByteArrayOutputStream out = new ByteArrayOutputStream(); Runtime.builder() @@ -211,7 +212,7 @@ void should_print_error_message_for_undefined_steps() { .run(); assertThat(out, bytesContainsString("" + - "##teamcity[testFailed timestamp = '1970-01-01T12:00:00.000+0000' duration = '0' message = 'Step undefined' details = 'You can implement missing steps with the snippets below:|n|n' name = 'first step']\n")); + "##teamcity[testFailed timestamp = '1970-01-01T12:00:00.000+0000' duration = '0' message = 'Step undefined' details = 'You can implement this step and 1 other step(s)using the snippet(s) below:|n|ntest snippet 0|ntest snippet 1|n' name = 'first step']")); } @Test diff --git a/core/src/test/java/io/cucumber/core/snippets/TestSnippet.java b/core/src/test/java/io/cucumber/core/snippets/TestSnippet.java index 5de28bdcb1..e224a735ce 100644 --- a/core/src/test/java/io/cucumber/core/snippets/TestSnippet.java +++ b/core/src/test/java/io/cucumber/core/snippets/TestSnippet.java @@ -8,9 +8,11 @@ public class TestSnippet implements Snippet { + private int i; + @Override public MessageFormat template() { - return new MessageFormat(""); + return new MessageFormat("test snippet " + i++); } @Override diff --git a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/UndefinedStepException.java b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/UndefinedStepException.java index fe56a619e9..b214792614 100644 --- a/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/UndefinedStepException.java +++ b/junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/UndefinedStepException.java @@ -3,40 +3,41 @@ import io.cucumber.core.runtime.TestCaseResultObserver.Suggestion; import org.opentest4j.IncompleteExecutionException; -import java.util.List; - -import static java.util.stream.Collectors.joining; +import java.util.Collection; +import java.util.stream.Collectors; final class UndefinedStepException extends IncompleteExecutionException { private static final long serialVersionUID = 1L; - UndefinedStepException(List suggestions) { + UndefinedStepException(Collection suggestions) { super(createMessage(suggestions)); } - private static String createMessage(List suggestions) { - return suggestions.stream() - .map(suggestion -> createStepMessage(suggestion.getStep(), suggestion.getSnippets())) - .collect(joining("\n", createPreAmble(suggestions), "")); - } - - private static String createStepMessage(String stepText, List snippets) { - StringBuilder sb = new StringBuilder("The step \"" + stepText + "\" is undefined"); - appendSnippets(snippets, sb); - return sb.toString(); - } - - private static String createPreAmble(List suggestions) { - return suggestions.size() < 2 ? "" : "There were " + suggestions.size() + " undefined steps\n"; - } - - private static void appendSnippets(List snippets, StringBuilder sb) { - if (snippets.isEmpty()) { - return; + private static String createMessage(Collection suggestions) { + if (suggestions.isEmpty()) { + return "This step is undefined"; + } + Suggestion first = suggestions.iterator().next(); + StringBuilder sb = new StringBuilder("The step '" + first.getStep() + "'"); + if (suggestions.size() == 1) { + sb.append(" is undefined."); + } else { + sb.append(" and ").append(suggestions.size() - 1).append(" other step(s) are undefined."); } - sb.append(". You can implement it using the snippet(s) below:\n\n"); - sb.append(snippets.stream().collect(joining("\n---\n", "", "\n"))); + sb.append("\n"); + if (suggestions.size() == 1) { + sb.append("You can implement this step using the snippet(s) below:\n\n"); + } else { + sb.append("You can implement these steps using the snippet(s) below:\n\n"); + } + String snippets = suggestions + .stream() + .map(Suggestion::getSnippets) + .flatMap(Collection::stream) + .distinct() + .collect(Collectors.joining("\n", "", "\n")); + sb.append(snippets); + return sb.toString(); } - } diff --git a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/TestCaseResultObserverTest.java b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/TestCaseResultObserverTest.java index 23e33a96c0..e2592051e6 100644 --- a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/TestCaseResultObserverTest.java +++ b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/TestCaseResultObserverTest.java @@ -7,6 +7,7 @@ import io.cucumber.plugin.event.PickleStepTestStep; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.Step; import io.cucumber.plugin.event.StepArgument; @@ -215,10 +216,11 @@ void undefined() { uri, testCase.getLocation(), testStep.getStep().getLocation(), - asList( - "mocked snippet 1", - "mocked snippet 2", - "mocked snippet 3"))); + new Suggestion(testStep.getStep().getText(), + asList( + "mocked snippet 1", + "mocked snippet 2", + "mocked snippet 3")))); Result result = new Result(Status.UNDEFINED, Duration.ZERO, null); bus.send(new TestStepFinished(Instant.now(), testCase, testStep, result)); bus.send(new TestCaseFinished(Instant.now(), testCase, result)); @@ -226,12 +228,11 @@ void undefined() { assertThat(exception.getCause(), instanceOf(UndefinedStepException.class)); assertThat(exception.getCause().getMessage(), is("" + - "The step \"mocked\" is undefined. You can implement it using the snippet(s) below:\n" + + "The step 'mocked' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + "\n" + "mocked snippet 1\n" + - "---\n" + "mocked snippet 2\n" + - "---\n" + "mocked snippet 3\n")); } diff --git a/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/UndefinedStepExceptionTest.java b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/UndefinedStepExceptionTest.java new file mode 100644 index 0000000000..88120674de --- /dev/null +++ b/junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/UndefinedStepExceptionTest.java @@ -0,0 +1,99 @@ +package io.cucumber.junit.platform.engine; + +import io.cucumber.core.runtime.TestCaseResultObserver.Suggestion; +import org.junit.jupiter.api.Test; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +class UndefinedStepExceptionTest { + + @Test + void should_generate_a_message_for_no_suggestions() { + UndefinedStepException exception = new UndefinedStepException(emptyList()); + assertThat(exception.getMessage(), is("This step is undefined")); + } + + @Test + void should_generate_a_message_for_one_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + singletonList( + new Suggestion("some step", singletonList("some snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + + "\n" + + "some snippet\n")); + } + + @Test + void should_generate_a_message_for_one_suggestions_with_multiple_snippets() { + UndefinedStepException exception = new UndefinedStepException( + singletonList( + new Suggestion("some step", asList("some snippet", "some other snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + + "\n" + + "some snippet\n" + + "some other snippet\n")); + } + + @Test + void should_generate_a_message_for_two_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + asList( + new Suggestion("some step", singletonList("some snippet")), + new Suggestion("some other step", singletonList("some other snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' and 1 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + + "\n" + + "some snippet\n" + + "some other snippet\n")); + } + + @Test + void should_generate_a_message_without_duplicate_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + asList( + new Suggestion("some step", asList("some snippet", "some snippet")), + new Suggestion("some other step", asList("some other snippet", "some other snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' and 1 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + + "\n" + + "some snippet\n" + + "some other snippet\n")); + } + + @Test + void should_generate_a_message_for_three_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + asList( + new Suggestion("some step", singletonList("some snippet")), + new Suggestion("some other step", singletonList("some other snippet")), + new Suggestion("yet another step", singletonList("yet another snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' and 2 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + + "\n" + + "some snippet\n" + + "some other snippet\n" + + "yet another snippet\n")); + } + +} diff --git a/junit/src/main/java/io/cucumber/junit/JUnitReporter.java b/junit/src/main/java/io/cucumber/junit/JUnitReporter.java index 13e87bd319..ac718ec7bd 100644 --- a/junit/src/main/java/io/cucumber/junit/JUnitReporter.java +++ b/junit/src/main/java/io/cucumber/junit/JUnitReporter.java @@ -6,6 +6,7 @@ import io.cucumber.plugin.event.PickleStepTestStep; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import io.cucumber.plugin.event.TestCaseFinished; import io.cucumber.plugin.event.TestCaseStarted; import io.cucumber.plugin.event.TestStep; @@ -20,8 +21,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import static io.cucumber.junit.SkippedThrowable.NotificationLevel.SCENARIO; import static io.cucumber.junit.SkippedThrowable.NotificationLevel.STEP; @@ -30,7 +29,7 @@ final class JUnitReporter { private final JUnitOptions junitOptions; private final EventBus bus; - private final Map> snippetsPerStep = new TreeMap<>(); + private final Collection suggestions = new ArrayList<>(); private final EventHandler snippetsSuggestedEventEventHandler = this::handleSnippetSuggested; private List stepErrors; private final EventHandler testCaseStartedHandler = this::handleTestCaseStarted; @@ -53,10 +52,7 @@ final class JUnitReporter { } private void handleSnippetSuggested(SnippetsSuggestedEvent snippetsSuggestedEvent) { - snippetsPerStep.putIfAbsent(new StepLocation( - snippetsSuggestedEvent.getUri(), - snippetsSuggestedEvent.getStepLine()), - snippetsSuggestedEvent.getSnippets()); + suggestions.add(snippetsSuggestedEvent.getSuggestion()); } void finishExecutionUnit() { @@ -123,13 +119,8 @@ private void handleStepResult(PickleStepTestStep testStep, Result result) { stepNotifier.addFailure(error); break; case UNDEFINED: - Collection snippets = snippetsPerStep.remove( - new StepLocation(testStep.getUri(), testStep.getStepLine())); - stepErrors.add(new UndefinedStepException( - testStep.getStepText(), - snippets, - snippetsPerStep.values())); - stepNotifier.addFailure(error == null ? new UndefinedStepException(snippets) : error); + stepErrors.add(new UndefinedStepException(suggestions)); + stepNotifier.addFailure(error == null ? new UndefinedStepException(suggestions) : error); break; default: throw new IllegalStateException("Unexpected result status: " + result.getStatus()); diff --git a/junit/src/main/java/io/cucumber/junit/UndefinedStepException.java b/junit/src/main/java/io/cucumber/junit/UndefinedStepException.java index be3235c20e..1b6e8a3174 100644 --- a/junit/src/main/java/io/cucumber/junit/UndefinedStepException.java +++ b/junit/src/main/java/io/cucumber/junit/UndefinedStepException.java @@ -1,65 +1,42 @@ package io.cucumber.junit; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; + import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; import java.util.stream.Collectors; final class UndefinedStepException extends RuntimeException { private static final long serialVersionUID = 1L; - UndefinedStepException(Collection snippets) { - super(createMessage(snippets), null, false, false); + UndefinedStepException(Collection suggestions) { + super(createMessage(suggestions), null, false, false); } - private static String createMessage(Collection snippets) { - StringBuilder sb = new StringBuilder("This step is undefined"); - appendSnippets(snippets, sb); - return sb.toString(); - } - - private static void appendSnippets(Collection snippets, StringBuilder sb) { - if (snippets.isEmpty()) { - return; + private static String createMessage(Collection suggestions) { + if (suggestions.isEmpty()) { + return "This step is undefined"; } - sb.append(". You can implement it using the snippet(s) below:\n\n"); - snippets.forEach(snippet -> { - sb.append(snippet); - sb.append("\n"); - }); - } - - UndefinedStepException(String stepText, Collection snippets, Collection> otherSnippets) { - super(createMessage(stepText, snippets, otherSnippets), null, false, false); - } - - private static String createMessage( - String stepText, Collection snippets, Collection> otherSnippets - ) { - Set otherUniqueSnippets = otherSnippets.stream() - .flatMap(Collection::stream) - .collect(Collectors.toCollection(LinkedHashSet::new)); - - otherUniqueSnippets.removeAll(snippets); - - StringBuilder sb = new StringBuilder("The step \"" + stepText + "\" is undefined"); - appendSnippets(snippets, sb); - appendOtherSnippets(otherUniqueSnippets, sb); - return sb.toString(); - } - - private static void appendOtherSnippets(Collection otherSnippets, StringBuilder sb) { - if (otherSnippets.isEmpty()) { - return; + Suggestion first = suggestions.iterator().next(); + StringBuilder sb = new StringBuilder("The step '" + first.getStep() + "'"); + if (suggestions.size() == 1) { + sb.append(" is undefined."); + } else { + sb.append(" and ").append(suggestions.size() - 1).append(" other step(s) are undefined."); } sb.append("\n"); - sb.append("\n"); - sb.append("Some other steps were also undefined:\n\n"); - otherSnippets.forEach(snippet -> { - sb.append(snippet); - sb.append("\n"); - }); + if (suggestions.size() == 1) { + sb.append("You can implement this step using the snippet(s) below:\n\n"); + } else { + sb.append("You can implement these steps using the snippet(s) below:\n\n"); + } + String snippets = suggestions + .stream() + .map(Suggestion::getSnippets) + .flatMap(Collection::stream) + .distinct() + .collect(Collectors.joining("\n", "", "\n")); + sb.append(snippets); + return sb.toString(); } - } diff --git a/junit/src/test/java/io/cucumber/junit/JUnitReporterWithStepNotificationsTest.java b/junit/src/test/java/io/cucumber/junit/JUnitReporterWithStepNotificationsTest.java index e1ba77b31b..5ad7f6357b 100644 --- a/junit/src/test/java/io/cucumber/junit/JUnitReporterWithStepNotificationsTest.java +++ b/junit/src/test/java/io/cucumber/junit/JUnitReporterWithStepNotificationsTest.java @@ -7,9 +7,11 @@ import io.cucumber.core.runtime.TimeServiceEventBus; import io.cucumber.junit.PickleRunners.PickleRunner; import io.cucumber.plugin.event.HookTestStep; +import io.cucumber.plugin.event.Location; import io.cucumber.plugin.event.PickleStepTestStep; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import io.cucumber.plugin.event.Status; import io.cucumber.plugin.event.TestCase; import io.cucumber.plugin.event.TestCaseFinished; @@ -53,7 +55,7 @@ @ExtendWith(MockitoExtension.class) class JUnitReporterWithStepNotificationsTest { - private static final int scenarioLine = 0; + private static final Location scenarioLine = new Location(0, 0); private static final URI featureUri = URI.create("file:example.feature"); private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); private final JUnitReporter jUnitReporter = new JUnitReporter(bus, @@ -93,8 +95,6 @@ void test_step_started_fires_test_started_for_step() { private static PickleStepTestStep mockTestStep(Step step) { PickleStepTestStep testStep = mock(PickleStepTestStep.class); - lenient().when(testStep.getStepText()).thenReturn(step.getText()); - lenient().when(testStep.getStepLine()).thenReturn(scenarioLine); lenient().when(testStep.getUri()).thenReturn(featureUri); lenient().when(testStep.getStep()).thenReturn(step); return testStep; @@ -228,8 +228,8 @@ void test_step_finished_fires_test_failure_and_test_finished_for_skipped_step_wi void test_step_undefined_fires_test_failure_and_test_finished_for_undefined_step() { jUnitReporter.startExecutionUnit(pickleRunner, runNotifier); - bus.send( - new SnippetsSuggestedEvent(now(), featureUri, scenarioLine, scenarioLine, singletonList("some snippet"))); + Suggestion suggestion = new Suggestion("step name", singletonList("some snippet")); + bus.send(new SnippetsSuggestedEvent(now(), featureUri, scenarioLine, scenarioLine, suggestion)); bus.send(new TestCaseStarted(now(), testCase)); bus.send(new TestStepStarted(now(), testCase, mockTestStep(step))); Throwable exception = new CucumberException("No step definitions found"); @@ -251,7 +251,8 @@ void test_step_undefined_fires_test_failure_and_test_finished_for_undefined_step Failure pickleFailure = failureArgumentCaptor.getValue(); assertThat(pickleFailure.getDescription(), is(equalTo(pickleRunner.getDescription()))); assertThat(pickleFailure.getException().getMessage(), is("" + - "The step \"step name\" is undefined. You can implement it using the snippet(s) below:\n" + + "The step 'step name' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + "\n" + "some snippet\n")); } @@ -265,8 +266,8 @@ void test_step_undefined_fires_test_failure_and_test_finished_for_undefined_step jUnitReporter.startExecutionUnit(pickleRunner, runNotifier); - bus.send( - new SnippetsSuggestedEvent(now(), featureUri, scenarioLine, scenarioLine, singletonList("some snippet"))); + Suggestion suggestion = new Suggestion("step name", singletonList("some snippet")); + bus.send(new SnippetsSuggestedEvent(now(), featureUri, scenarioLine, scenarioLine, suggestion)); bus.send(new TestCaseStarted(now(), testCase)); bus.send(new TestStepStarted(now(), testCase, mockTestStep(step))); Throwable exception = new CucumberException("No step definitions found"); @@ -288,7 +289,8 @@ void test_step_undefined_fires_test_failure_and_test_finished_for_undefined_step Failure pickleFailure = failureArgumentCaptor.getValue(); assertThat(pickleFailure.getDescription(), is(equalTo(pickleRunner.getDescription()))); assertThat(pickleFailure.getException().getMessage(), is("" + - "The step \"step name\" is undefined. You can implement it using the snippet(s) below:\n" + + "The step 'step name' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + "\n" + "some snippet\n")); } diff --git a/junit/src/test/java/io/cucumber/junit/UndefinedStepExceptionTest.java b/junit/src/test/java/io/cucumber/junit/UndefinedStepExceptionTest.java index 5e7131a581..9b325e3e0a 100644 --- a/junit/src/test/java/io/cucumber/junit/UndefinedStepExceptionTest.java +++ b/junit/src/test/java/io/cucumber/junit/UndefinedStepExceptionTest.java @@ -1,7 +1,9 @@ package io.cucumber.junit; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import org.junit.jupiter.api.Test; +import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; @@ -10,53 +12,88 @@ class UndefinedStepExceptionTest { @Test - void should_generate_a_message_for_a_single_snippet() { - UndefinedStepException exception = new UndefinedStepException(singletonList("snippet")); + void should_generate_a_message_for_no_suggestions() { + UndefinedStepException exception = new UndefinedStepException(emptyList()); + assertThat(exception.getMessage(), is("This step is undefined")); + } + + @Test + void should_generate_a_message_for_one_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + singletonList( + new Suggestion("some step", singletonList("some snippet"))) + + ); assertThat(exception.getMessage(), is("" + - "This step is undefined. You can implement it using the snippet(s) below:\n" + + "The step 'some step' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + "\n" + - "snippet\n")); + "some snippet\n")); } @Test - void should_generate_a_message_for_step_without_additional_snippets() { + void should_generate_a_message_for_one_suggestions_with_multiple_snippets() { UndefinedStepException exception = new UndefinedStepException( - "step text", - singletonList("snippet"), - emptyList()); + singletonList( + new Suggestion("some step", asList("some snippet", "some other snippet"))) + + ); assertThat(exception.getMessage(), is("" + - "The step \"step text\" is undefined. You can implement it using the snippet(s) below:\n" + + "The step 'some step' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + "\n" + - "snippet\n")); + "some snippet\n" + + "some other snippet\n")); } @Test - void should_generate_a_message_for_step_with_additional_snippets() { + void should_generate_a_message_for_two_suggestions() { UndefinedStepException exception = new UndefinedStepException( - "step text", - singletonList("snippet"), - singletonList(singletonList("additional snippet"))); + asList( + new Suggestion("some step", singletonList("some snippet")), + new Suggestion("some other step", singletonList("some other snippet"))) + + ); assertThat(exception.getMessage(), is("" + - "The step \"step text\" is undefined. You can implement it using the snippet(s) below:\n" + - "\n" + - "snippet\n" + + "The step 'some step' and 1 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + "\n" + + "some snippet\n" + + "some other snippet\n")); + } + + @Test + void should_generate_a_message_without_duplicate_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + asList( + new Suggestion("some step", asList("some snippet", "some snippet")), + new Suggestion("some other step", asList("some other snippet", "some other snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' and 1 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + "\n" + - "Some other steps were also undefined:\n" + - "\n" + - "additional snippet\n")); + "some snippet\n" + + "some other snippet\n")); } @Test - void should_generate_a_message_for_step_with_additional_duplicated_snippets() { + void should_generate_a_message_for_three_suggestions() { UndefinedStepException exception = new UndefinedStepException( - "step text", - singletonList("snippet"), - singletonList(singletonList("snippet"))); + asList( + new Suggestion("some step", singletonList("some snippet")), + new Suggestion("some other step", singletonList("some other snippet")), + new Suggestion("yet another step", singletonList("yet another snippet"))) + + ); assertThat(exception.getMessage(), is("" + - "The step \"step text\" is undefined. You can implement it using the snippet(s) below:\n" + + "The step 'some step' and 2 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + "\n" + - "snippet\n")); + "some snippet\n" + + "some other snippet\n" + + "yet another snippet\n")); } } diff --git a/plugin/src/main/java/io/cucumber/plugin/event/SnippetsSuggestedEvent.java b/plugin/src/main/java/io/cucumber/plugin/event/SnippetsSuggestedEvent.java index 336a9583a8..13d89337e4 100644 --- a/plugin/src/main/java/io/cucumber/plugin/event/SnippetsSuggestedEvent.java +++ b/plugin/src/main/java/io/cucumber/plugin/event/SnippetsSuggestedEvent.java @@ -13,23 +13,30 @@ public final class SnippetsSuggestedEvent extends TimeStampedEvent { private final URI uri; - private final Location scenarioLocation; + private final Location testCaseLocation; private final Location stepLocation; - private final List snippets; + private final Suggestion suggestion; @Deprecated public SnippetsSuggestedEvent(Instant timeInstant, URI uri, int scenarioLine, int stepLine, List snippets) { this(timeInstant, uri, new Location(scenarioLine, -1), new Location(stepLine, -1), snippets); } + @Deprecated + public SnippetsSuggestedEvent( + Instant instant, URI uri, Location testCaseLocation, Location stepLocation, List snippets + ) { + this(instant, uri, testCaseLocation, stepLocation, new Suggestion("", snippets)); + } + public SnippetsSuggestedEvent( - Instant instant, URI uri, Location scenarioLocation, Location stepLocation, List snippets + Instant instant, URI uri, Location testCaseLocation, Location stepLocation, Suggestion suggestion ) { super(instant); this.uri = requireNonNull(uri); - this.scenarioLocation = scenarioLocation; - this.stepLocation = stepLocation; - this.snippets = unmodifiableList(requireNonNull(snippets)); + this.testCaseLocation = requireNonNull(testCaseLocation); + this.stepLocation = requireNonNull(stepLocation); + this.suggestion = requireNonNull(suggestion); } public URI getUri() { @@ -43,19 +50,48 @@ public int getStepLine() { @Deprecated public int getScenarioLine() { - return scenarioLocation.getLine(); + return testCaseLocation.getLine(); } + @Deprecated public Location getScenarioLocation() { - return scenarioLocation; + return testCaseLocation; + } + + public Location getTestCaseLocation() { + return testCaseLocation; } public Location getStepLocation() { return stepLocation; } + @Deprecated public List getSnippets() { - return snippets; + return suggestion.getSnippets(); } + public Suggestion getSuggestion() { + return suggestion; + } + + public static final class Suggestion { + + final String step; + final List snippets; + + public Suggestion(String step, List snippets) { + this.step = requireNonNull(step); + this.snippets = unmodifiableList(requireNonNull(snippets)); + } + + public String getStep() { + return step; + } + + public List getSnippets() { + return snippets; + } + + } } diff --git a/testng/src/main/java/io/cucumber/testng/UndefinedStepException.java b/testng/src/main/java/io/cucumber/testng/UndefinedStepException.java index 2a577079ff..f1ce9fd7a0 100644 --- a/testng/src/main/java/io/cucumber/testng/UndefinedStepException.java +++ b/testng/src/main/java/io/cucumber/testng/UndefinedStepException.java @@ -3,41 +3,42 @@ import io.cucumber.core.runtime.TestCaseResultObserver.Suggestion; import org.testng.SkipException; -import java.util.List; - -import static java.util.stream.Collectors.joining; +import java.util.Collection; +import java.util.stream.Collectors; final class UndefinedStepException extends SkipException { private static final long serialVersionUID = 1L; - UndefinedStepException(List suggestions) { + UndefinedStepException(Collection suggestions) { super(createMessage(suggestions)); } - private static String createMessage(List suggestions) { - return suggestions.stream() - .map(suggestion -> createStepMessage(suggestion.getStep(), suggestion.getSnippets())) - .collect(joining("\n", createPreAmble(suggestions), "")); - } - - private static String createStepMessage(String stepText, List snippets) { - StringBuilder sb = new StringBuilder("The step \"" + stepText + "\" is undefined"); - appendSnippets(snippets, sb); - return sb.toString(); - } - - private static String createPreAmble(List suggestions) { - return suggestions.size() < 2 ? "" : "There were " + suggestions.size() + " undefined steps\n"; - } - - private static void appendSnippets(List snippets, StringBuilder sb) { - - if (snippets.isEmpty()) { - return; + private static String createMessage(Collection suggestions) { + if (suggestions.isEmpty()) { + return "This step is undefined"; } - sb.append(". You can implement it using the snippet(s) below:\n\n"); - sb.append(snippets.stream().collect(joining("\n---\n", "", "\n"))); + Suggestion first = suggestions.iterator().next(); + StringBuilder sb = new StringBuilder("The step '" + first.getStep() + "'"); + if (suggestions.size() == 1) { + sb.append(" is undefined."); + } else { + sb.append(" and ").append(suggestions.size() - 1).append(" other step(s) are undefined."); + } + sb.append("\n"); + if (suggestions.size() == 1) { + sb.append("You can implement this step using the snippet(s) below:\n\n"); + } else { + sb.append("You can implement these steps using the snippet(s) below:\n\n"); + } + String snippets = suggestions + .stream() + .map(Suggestion::getSnippets) + .flatMap(Collection::stream) + .distinct() + .collect(Collectors.joining("\n", "", "\n")); + sb.append(snippets); + return sb.toString(); } @Override diff --git a/testng/src/test/java/io/cucumber/testng/TestCaseResultObserverTest.java b/testng/src/test/java/io/cucumber/testng/TestCaseResultObserverTest.java index 1a06113638..7bdbd79844 100644 --- a/testng/src/test/java/io/cucumber/testng/TestCaseResultObserverTest.java +++ b/testng/src/test/java/io/cucumber/testng/TestCaseResultObserverTest.java @@ -2,10 +2,13 @@ import io.cucumber.core.eventbus.EventBus; import io.cucumber.core.runtime.TimeServiceEventBus; +import io.cucumber.plugin.event.Location; import io.cucumber.plugin.event.PickleStepTestStep; import io.cucumber.plugin.event.Result; import io.cucumber.plugin.event.SnippetsSuggestedEvent; +import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; import io.cucumber.plugin.event.Status; +import io.cucumber.plugin.event.Step; import io.cucumber.plugin.event.TestCase; import io.cucumber.plugin.event.TestCaseFinished; import io.cucumber.plugin.event.TestStepFinished; @@ -38,17 +41,18 @@ public class TestCaseResultObserverTest { private final EventBus bus = new TimeServiceEventBus(Clock.systemUTC(), UUID::randomUUID); private final URI uri = URI.create("file:path/to.feature"); - private final int line = 0; + private final Location location = new Location(0, -1); private final Exception error = new Exception(); private final TestCase testCase = mock(TestCase.class); private final PickleStepTestStep step = createPickleStepTestStep(); private PickleStepTestStep createPickleStepTestStep() { - PickleStepTestStep step = mock(PickleStepTestStep.class); - when(step.getStepLine()).thenReturn(line); - when(step.getUri()).thenReturn(uri); - when(step.getStepText()).thenReturn("some step"); - return step; + PickleStepTestStep testStep = mock(PickleStepTestStep.class); + Step step = mock(Step.class); + when(step.getLocation()).thenReturn(location); + when(testStep.getStep()).thenReturn(step); + when(testStep.getUri()).thenReturn(uri); + return testStep; } @Test @@ -96,7 +100,8 @@ public void should_not_be_passed_for_ambiguous_result() { public void should_be_failed_for_undefined_result() { TestCaseResultObserver resultListener = TestCaseResultObserver.observe(bus); - bus.send(new SnippetsSuggestedEvent(now(), uri, line, line, singletonList("stub snippet"))); + bus.send(new SnippetsSuggestedEvent(now(), uri, location, location, + new Suggestion("some step", singletonList("stub snippet")))); Result stepResult = new Result(UNDEFINED, ZERO, error); bus.send(new TestStepFinished(now(), testCase, step, stepResult)); @@ -109,7 +114,8 @@ public void should_be_failed_for_undefined_result() { SkipException skipException = (SkipException) exception.getCause(); assertThat(skipException.isSkip(), is(false)); assertThat(skipException.getMessage(), is("" + - "The step \"some step\" is undefined. You can implement it using the snippet(s) below:\n" + + "The step 'some step' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + "\n" + "stub snippet\n")); } @@ -118,7 +124,8 @@ public void should_be_failed_for_undefined_result() { public void should_not_be_skipped_for_undefined_result() { TestCaseResultObserver resultListener = TestCaseResultObserver.observe(bus); - bus.send(new SnippetsSuggestedEvent(now(), uri, line, line, singletonList("stub snippet"))); + bus.send(new SnippetsSuggestedEvent(now(), uri, location, + location, new SnippetsSuggestedEvent.Suggestion("some step", singletonList("stub snippet")))); Result stepResult = new Result(UNDEFINED, ZERO, error); bus.send(new TestStepFinished(now(), testCase, step, stepResult)); @@ -131,7 +138,8 @@ public void should_not_be_skipped_for_undefined_result() { SkipException skipException = (SkipException) exception.getCause(); assertThat(skipException.isSkip(), is(false)); assertThat(skipException.getMessage(), is("" + - "The step \"some step\" is undefined. You can implement it using the snippet(s) below:\n" + + "The step 'some step' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + "\n" + "stub snippet\n")); } diff --git a/testng/src/test/java/io/cucumber/testng/UndefinedStepExceptionTest.java b/testng/src/test/java/io/cucumber/testng/UndefinedStepExceptionTest.java new file mode 100644 index 0000000000..72a20d6bbc --- /dev/null +++ b/testng/src/test/java/io/cucumber/testng/UndefinedStepExceptionTest.java @@ -0,0 +1,99 @@ +package io.cucumber.testng; + +import io.cucumber.core.runtime.TestCaseResultObserver.Suggestion; +import org.testng.annotations.Test; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class UndefinedStepExceptionTest { + + @Test + public void should_generate_a_message_for_no_suggestions() { + UndefinedStepException exception = new UndefinedStepException(emptyList()); + assertThat(exception.getMessage(), is("This step is undefined")); + } + + @Test + public void should_generate_a_message_for_one_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + singletonList( + new Suggestion("some step", singletonList("some snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + + "\n" + + "some snippet\n")); + } + + @Test + public void should_generate_a_message_for_one_suggestions_with_multiple_snippets() { + UndefinedStepException exception = new UndefinedStepException( + singletonList( + new Suggestion("some step", asList("some snippet", "some other snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' is undefined.\n" + + "You can implement this step using the snippet(s) below:\n" + + "\n" + + "some snippet\n" + + "some other snippet\n")); + } + + @Test + public void should_generate_a_message_for_two_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + asList( + new Suggestion("some step", singletonList("some snippet")), + new Suggestion("some other step", singletonList("some other snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' and 1 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + + "\n" + + "some snippet\n" + + "some other snippet\n")); + } + + @Test + public void should_generate_a_message_without_duplicate_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + asList( + new Suggestion("some step", asList("some snippet", "some snippet")), + new Suggestion("some other step", asList("some other snippet", "some other snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' and 1 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + + "\n" + + "some snippet\n" + + "some other snippet\n")); + } + + @Test + public void should_generate_a_message_for_three_suggestions() { + UndefinedStepException exception = new UndefinedStepException( + asList( + new Suggestion("some step", singletonList("some snippet")), + new Suggestion("some other step", singletonList("some other snippet")), + new Suggestion("yet another step", singletonList("yet another snippet"))) + + ); + assertThat(exception.getMessage(), is("" + + "The step 'some step' and 2 other step(s) are undefined.\n" + + "You can implement these steps using the snippet(s) below:\n" + + "\n" + + "some snippet\n" + + "some other snippet\n" + + "yet another snippet\n")); + } + +}