diff --git a/java-reporter-core/pom.xml b/java-reporter-core/pom.xml index 1d3e6516..22c42b17 100644 --- a/java-reporter-core/pom.xml +++ b/java-reporter-core/pom.xml @@ -7,7 +7,7 @@ io.testomat java-reporter-core - 0.9.0 + 0.9.1 jar Testomat.io Reporter Core diff --git a/java-reporter-core/src/main/java/io/testomat/core/constants/CommonConstants.java b/java-reporter-core/src/main/java/io/testomat/core/constants/CommonConstants.java index 78806e95..90c05293 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/constants/CommonConstants.java +++ b/java-reporter-core/src/main/java/io/testomat/core/constants/CommonConstants.java @@ -1,7 +1,7 @@ package io.testomat.core.constants; public class CommonConstants { - public static final String REPORTER_VERSION = "0.8.7"; + public static final String REPORTER_VERSION = "0.9.1"; public static final String TESTS_STRING = "tests"; public static final String API_KEY_STRING = "api_key"; diff --git a/java-reporter-core/src/main/java/io/testomat/core/step/StepTimer.java b/java-reporter-core/src/main/java/io/testomat/core/step/StepTimer.java new file mode 100644 index 00000000..df613053 --- /dev/null +++ b/java-reporter-core/src/main/java/io/testomat/core/step/StepTimer.java @@ -0,0 +1,21 @@ +package io.testomat.core.step; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class StepTimer { + + private static final Map START_TIMES = new ConcurrentHashMap<>(); + + public static void start(String key) { + START_TIMES.put(key, System.currentTimeMillis()); + } + + public static long stop(String key) { + Long start = START_TIMES.remove(key); + if (start == null) { + return -1; + } + return System.currentTimeMillis() - start; + } +} diff --git a/java-reporter-cucumber/pom.xml b/java-reporter-cucumber/pom.xml index f0f68599..253b7011 100644 --- a/java-reporter-cucumber/pom.xml +++ b/java-reporter-cucumber/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter-cucumber - 0.7.10 + 0.7.11 jar Testomat.io Java Reporter Cucumber @@ -51,7 +51,7 @@ io.testomat java-reporter-core - 0.9.0 + 0.9.1 org.slf4j diff --git a/java-reporter-junit/pom.xml b/java-reporter-junit/pom.xml index 89711168..206fd410 100644 --- a/java-reporter-junit/pom.xml +++ b/java-reporter-junit/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter-junit - 0.8.1 + 0.8.2 jar Testomat.io Java Reporter JUnit @@ -51,7 +51,7 @@ io.testomat java-reporter-core - 0.9.0 + 0.9.1 org.slf4j diff --git a/java-reporter-karate/pom.xml b/java-reporter-karate/pom.xml index 7e417686..0051c503 100644 --- a/java-reporter-karate/pom.xml +++ b/java-reporter-karate/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter-karate - 0.2.2 + 0.2.3 jar Testomat.io Java Reporter Karate @@ -52,7 +52,7 @@ io.testomat java-reporter-core - 0.9.0 + 0.9.1 io.karatelabs @@ -117,7 +117,7 @@ maven-javadoc-plugin ${maven-javadoc-plugin.version} - 11 + 17 true none diff --git a/java-reporter-karate/src/main/java/io/testomat/karate/adapter/CustomKarateEngineAdapter.java b/java-reporter-karate/src/main/java/io/testomat/karate/adapter/CustomKarateEngineAdapter.java new file mode 100644 index 00000000..cc493e7a --- /dev/null +++ b/java-reporter-karate/src/main/java/io/testomat/karate/adapter/CustomKarateEngineAdapter.java @@ -0,0 +1,19 @@ +package io.testomat.karate.adapter; + +import com.intuit.karate.core.ScenarioEngine; +import com.intuit.karate.core.Variable; + +public class CustomKarateEngineAdapter implements KarateEngineAdapter { + + @Override + public Object getVariable(String name) { + ScenarioEngine engine = ScenarioEngine.get(); + Variable variable = engine.vars.get(name); + return variable != null ? variable.getValue() : null; + } + + @Override + public void setVariable(String name, Object value) { + ScenarioEngine.get().setVariable(name, value); + } +} diff --git a/java-reporter-karate/src/main/java/io/testomat/karate/adapter/KarateEngineAdapter.java b/java-reporter-karate/src/main/java/io/testomat/karate/adapter/KarateEngineAdapter.java new file mode 100644 index 00000000..b42a1d00 --- /dev/null +++ b/java-reporter-karate/src/main/java/io/testomat/karate/adapter/KarateEngineAdapter.java @@ -0,0 +1,8 @@ +package io.testomat.karate.adapter; + +public interface KarateEngineAdapter { + + Object getVariable(String name); + + void setVariable(String name, Object value); +} diff --git a/java-reporter-karate/src/main/java/io/testomat/karate/extractor/TestDataExtractor.java b/java-reporter-karate/src/main/java/io/testomat/karate/extractor/TestDataExtractor.java index 99679ce5..eb114669 100644 --- a/java-reporter-karate/src/main/java/io/testomat/karate/extractor/TestDataExtractor.java +++ b/java-reporter-karate/src/main/java/io/testomat/karate/extractor/TestDataExtractor.java @@ -3,7 +3,6 @@ import static io.testomat.core.constants.CommonConstants.FAILED; import static io.testomat.core.constants.CommonConstants.PASSED; import static io.testomat.core.constants.CommonConstants.SKIPPED; -import static java.util.Objects.isNull; import com.intuit.karate.core.ScenarioResult; import com.intuit.karate.core.ScenarioRuntime; @@ -12,9 +11,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; -import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.UUID; import java.util.stream.Stream; import org.slf4j.Logger; @@ -47,47 +44,21 @@ public ExceptionDetails extractExceptionDetails(ScenarioRuntime sr) { } /** - * Extracts a test identifier from a Karate test execution context. + * Extracts a test identifier from scenario tags. *

- * The method attempts to resolve the test identifier using the following priority: - *

    - *
  1. - * Scenario Outline {@code Examples} data — looks for an example column - * whose name matches the configured test id prefix. - *
  2. - *
  3. - * Scenario tags — looks for tags starting with the same prefix - * (for example, {@code TestId:}). - *
  4. - *
- *

- * The extracted value is validated against the configured test id format. - * No additional normalization is applied to the returned value. - *

- * If no valid test identifier is found in either source, the method returns {@code null}. + * Searches for the first tag that starts with the configured {@code TEST_ID_PREFIX} + * (case-insensitive), removes the prefix, and validates the remaining value. * - * @param sr the {@link ScenarioRuntime} representing the executed Karate test case - * @return the extracted test identifier, or {@code null} if not found + * @param sr the {@link ScenarioRuntime} of the executed Karate scenario + * @return the extracted test identifier, or {@code null} if none is found */ public String extractTestId(ScenarioRuntime sr) { - String testId = findFirstValidTestId( - Optional.ofNullable(sr.scenario.getExampleData()) - .stream() - .flatMap(m -> m.entrySet().stream()) - .filter(e -> e.getKey().equalsIgnoreCase(TEST_ID_PREFIX)) - .map(Map.Entry::getValue) - .map(Object::toString) - ); - - if (isNull(testId)) { - testId = findFirstValidTestId(sr.tags.getTags().stream() - .filter(Objects::nonNull) - .filter(tag -> tag.regionMatches(true, 0, - TEST_ID_PREFIX, 0, TEST_ID_PREFIX.length())) - .map(tag -> tag.substring(TEST_ID_PREFIX.length() + 1))); - } + return findFirstValidTestId(sr.tags.getTags().stream() + .filter(Objects::nonNull) + .filter(tag -> tag.regionMatches(true, 0, + TEST_ID_PREFIX, 0, TEST_ID_PREFIX.length())) + .map(tag -> tag.substring(TEST_ID_PREFIX.length() + 1))); - return testId; } /** diff --git a/java-reporter-karate/src/main/java/io/testomat/karate/hooks/KarateHook.java b/java-reporter-karate/src/main/java/io/testomat/karate/hooks/KarateHook.java index 207112df..76d3a912 100644 --- a/java-reporter-karate/src/main/java/io/testomat/karate/hooks/KarateHook.java +++ b/java-reporter-karate/src/main/java/io/testomat/karate/hooks/KarateHook.java @@ -5,30 +5,49 @@ import com.intuit.karate.RuntimeHook; import com.intuit.karate.Suite; import com.intuit.karate.core.ScenarioRuntime; +import com.intuit.karate.core.Step; +import com.intuit.karate.core.StepResult; import io.testomat.core.exception.ReportTestResultException; import io.testomat.core.model.TestResult; import io.testomat.core.runmanager.GlobalRunManager; +import io.testomat.core.step.StepStorage; +import io.testomat.core.step.StepTimer; +import io.testomat.core.step.TestStep; +import io.testomat.karate.adapter.CustomKarateEngineAdapter; +import io.testomat.karate.adapter.KarateEngineAdapter; import io.testomat.karate.constructor.KarateTestResultConstructor; import io.testomat.karate.exception.KarateHookException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Runtime hook for integrating Karate test execution with Testomat.io. - * Reports Karate test execution results to the Testomat.io platform. + * Reports Karate test execution results to the + * Testomat.io platform. */ public class KarateHook implements RuntimeHook { + private static final Logger log = LoggerFactory.getLogger(KarateHook.class); + + private static final String LOG_NEXT_STEP = "log_next_step"; + private static final String LOG_NEXT_STEP_TITLE = "log_next_step_title"; + private static final String LOG_STEPS = "LogSteps"; + private final KarateTestResultConstructor resultConstructor; private final FacadeFunctionsHandler functionsHandler; private final GlobalRunManager runManager; + private final KarateEngineAdapter engine; public KarateHook( KarateTestResultConstructor resultConstructor, FacadeFunctionsHandler functionsHandler, - GlobalRunManager runManager + GlobalRunManager runManager, + KarateEngineAdapter engine ) { this.resultConstructor = resultConstructor; this.functionsHandler = functionsHandler; this.runManager = runManager; + this.engine = engine; } /** @@ -39,7 +58,8 @@ public KarateHook() { this( new KarateTestResultConstructor(), new FacadeFunctionsHandler(), - GlobalRunManager.getInstance() + GlobalRunManager.getInstance(), + new CustomKarateEngineAdapter() ); } @@ -48,6 +68,51 @@ public void beforeSuite(Suite suite) { runManager.incrementSuiteCounter(); } + @Override + public boolean beforeStep(Step step, ScenarioRuntime sr) { + boolean isMarked = Boolean.TRUE.equals(engine.getVariable(LOG_NEXT_STEP)); + + boolean isDslStep = switch (step.getPrefix() == null ? "" : step.getPrefix()) { + case "Given", "When", "Then", "And", "But" -> true; + default -> false; + }; + + boolean logAllSteps = isDslStep && sr.tags.getTags().contains(LOG_STEPS); + + if (logAllSteps || isMarked) { + engine.setVariable(LOG_NEXT_STEP, false); + String stepId = Thread.currentThread().getId() + ":" + System.identityHashCode(step); + StepTimer.start(stepId); + } + return true; + } + + @Override + public void afterStep(StepResult result, ScenarioRuntime sr) { + Step step = result.getStep(); + String stepId = Thread.currentThread().getId() + ":" + System.identityHashCode(step); + long durationMillis = StepTimer.stop(stepId); + + if (durationMillis != -1) { + String stepName = step.getText(); + Object title = engine.getVariable(LOG_NEXT_STEP_TITLE); + + if (title != null) { + stepName = title.toString(); + engine.setVariable(LOG_NEXT_STEP_TITLE, null); + } + + TestStep testStep = new TestStep(); + testStep.setCategory("user"); + testStep.setStepTitle(stepName); + testStep.setDuration(durationMillis); + + StepStorage.addStep(testStep); + + log.debug("Step '{}' completed in {} ms", stepName, durationMillis); + } + } + @Override public void afterScenario(ScenarioRuntime sr) { if (!runManager.isActive()) { diff --git a/java-reporter-karate/src/main/java/io/testomat/karate/marker/StepMarker.java b/java-reporter-karate/src/main/java/io/testomat/karate/marker/StepMarker.java new file mode 100644 index 00000000..275e4106 --- /dev/null +++ b/java-reporter-karate/src/main/java/io/testomat/karate/marker/StepMarker.java @@ -0,0 +1,28 @@ +package io.testomat.karate.marker; + +import com.intuit.karate.core.ScenarioEngine; + +public final class StepMarker { + + private static final String LOG_NEXT_STEP = "log_next_step"; + private static final String LOG_NEXT_STEP_TITLE = "log_next_step_title"; + + private StepMarker() { + + } + + public static void mark() { + ScenarioEngine engine = ScenarioEngine.get(); + if (engine != null) { + engine.setVariable(LOG_NEXT_STEP, true); + } + } + + public static void mark(String title) { + ScenarioEngine engine = ScenarioEngine.get(); + if (engine != null) { + engine.setVariable(LOG_NEXT_STEP, true); + engine.setVariable(LOG_NEXT_STEP_TITLE, title); + } + } +} diff --git a/java-reporter-karate/src/test/java/io/testomat/karate/adapter/CustomKarateEngineAdapterTest.java b/java-reporter-karate/src/test/java/io/testomat/karate/adapter/CustomKarateEngineAdapterTest.java new file mode 100644 index 00000000..282ec937 --- /dev/null +++ b/java-reporter-karate/src/test/java/io/testomat/karate/adapter/CustomKarateEngineAdapterTest.java @@ -0,0 +1,70 @@ +package io.testomat.karate.adapter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.intuit.karate.core.ScenarioEngine; +import com.intuit.karate.core.Variable; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +class CustomKarateEngineAdapterTest { + + CustomKarateEngineAdapter adapter = new CustomKarateEngineAdapter(); + + @Test + void shouldReturnVariableValue() throws Exception { + ScenarioEngine engine = mock(ScenarioEngine.class); + Variable variable = mock(Variable.class); + + when(variable.getValue()).thenReturn("value"); + + Map vars = new HashMap<>(); + vars.put("test", variable); + + Field varsField = ScenarioEngine.class.getDeclaredField("vars"); + varsField.setAccessible(true); + varsField.set(engine, vars); + + try (MockedStatic mocked = mockStatic(ScenarioEngine.class)) { + mocked.when(ScenarioEngine::get).thenReturn(engine); + Object result = adapter.getVariable("test"); + assertEquals("value", result); + } + } + + @Test + void shouldReturnNullWhenVariableMissing() throws Exception { + ScenarioEngine engine = mock(ScenarioEngine.class); + + Map vars = new HashMap<>(); + + Field varsField = ScenarioEngine.class.getDeclaredField("vars"); + varsField.setAccessible(true); + varsField.set(engine, vars); + + try (MockedStatic mocked = mockStatic(ScenarioEngine.class)) { + mocked.when(ScenarioEngine::get).thenReturn(engine); + Object result = adapter.getVariable("missing"); + assertNull(result); + } + } + + @Test + void shouldSetVariable() { + ScenarioEngine engine = mock(ScenarioEngine.class); + + try (MockedStatic mocked = mockStatic(ScenarioEngine.class)) { + mocked.when(ScenarioEngine::get).thenReturn(engine); + adapter.setVariable("key", "value"); + verify(engine).setVariable("key", "value"); + } + } +} diff --git a/java-reporter-karate/src/test/java/io/testomat/karate/extractor/TestDataExtractorTest.java b/java-reporter-karate/src/test/java/io/testomat/karate/extractor/TestDataExtractorTest.java index 99b5da0c..8e7330a3 100644 --- a/java-reporter-karate/src/test/java/io/testomat/karate/extractor/TestDataExtractorTest.java +++ b/java-reporter-karate/src/test/java/io/testomat/karate/extractor/TestDataExtractorTest.java @@ -12,8 +12,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; @@ -59,83 +57,6 @@ void extractExceptionDetailsWhenPassed() { .isEqualTo(ExceptionDetails.empty()); } - @Test - void extractTestIdFromExampleDataHasPriorityOverTags() { - ScenarioRuntime sr = mockScenarioRuntimeWithTags("@testId:Taaaa1111"); - - Map exampleData = new HashMap<>(); - exampleData.put("testId", "Tbbbb2222"); - - when(sr.scenario.getExampleData()).thenReturn(exampleData); - - String testId = extractor.extractTestId(sr); - - assertThat(testId).isEqualTo("Tbbbb2222"); - } - - @Test - void extractTestIdFromExampleDataOnly() { - ScenarioRuntime sr = mockScenarioRuntime(); - - Map exampleData = new HashMap<>(); - exampleData.put("testId", "Tabcd1234"); - - when(sr.scenario.getExampleData()).thenReturn(exampleData); - - String testId = extractor.extractTestId(sr); - - assertThat(testId).isEqualTo("Tabcd1234"); - } - - @Test - void extractTestIdFromTagsWhenExampleDataIsNull() { - ScenarioRuntime sr = - mockScenarioRuntimeWithTags("@testId:Tabc12345", "smoke"); - - when(sr.scenario.getExampleData()).thenReturn(null); - - String testId = extractor.extractTestId(sr); - - assertThat(testId).isEqualTo("Tabc12345"); - } - - @Test - void extractTestIdFromTagsWhenExampleDataDoesNotContainTestId() { - ScenarioRuntime sr = - mockScenarioRuntimeWithTags("@testId:Tcccc3333"); - - Map exampleData = new HashMap<>(); - exampleData.put("other", "value"); - - when(sr.scenario.getExampleData()).thenReturn(exampleData); - - String testId = extractor.extractTestId(sr); - - assertThat(testId).isEqualTo("Tcccc3333"); - } - - @Test - void extractTestIdReturnsNullWhenNotFoundAnywhere() { - ScenarioRuntime sr = - mockScenarioRuntimeWithTags("smoke", "regression"); - - when(sr.scenario.getExampleData()).thenReturn(null); - - assertThat(extractor.extractTestId(sr)).isNull(); - } - - @Test - void extractTestIdIgnoresInvalidFormat() { - ScenarioRuntime sr = - mockScenarioRuntimeWithTags("testId:@W11112222", "smoke"); - - when(sr.scenario.getExampleData()).thenReturn(null); - - String testId = extractor.extractTestId(sr); - - assertThat(testId).isNull(); - } - @Test void extractTestIdNotFound() { ScenarioRuntime sr = mockScenarioRuntimeWithTags("smoke", "regression"); diff --git a/java-reporter-karate/src/test/java/io/testomat/karate/hooks/KarateHookTest.java b/java-reporter-karate/src/test/java/io/testomat/karate/hooks/KarateHookTest.java index 6944975b..63ae9a75 100644 --- a/java-reporter-karate/src/test/java/io/testomat/karate/hooks/KarateHookTest.java +++ b/java-reporter-karate/src/test/java/io/testomat/karate/hooks/KarateHookTest.java @@ -1,28 +1,48 @@ package io.testomat.karate.hooks; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import com.intuit.karate.Suite; import com.intuit.karate.core.Scenario; +import com.intuit.karate.core.ScenarioEngine; import com.intuit.karate.core.ScenarioRuntime; +import com.intuit.karate.core.Step; +import com.intuit.karate.core.StepResult; +import com.intuit.karate.core.Tags; import io.testomat.core.exception.ReportTestResultException; import io.testomat.core.model.TestResult; import io.testomat.core.runmanager.GlobalRunManager; +import io.testomat.core.step.StepStorage; +import io.testomat.core.step.StepTimer; +import io.testomat.core.step.TestStep; +import io.testomat.karate.adapter.CustomKarateEngineAdapter; +import io.testomat.karate.adapter.KarateEngineAdapter; import io.testomat.karate.constructor.KarateTestResultConstructor; import io.testomat.karate.exception.KarateHookException; import java.lang.reflect.Field; +import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; class KarateHookTest { KarateTestResultConstructor constructor = mock(KarateTestResultConstructor.class); FacadeFunctionsHandler functionsHandler = mock(FacadeFunctionsHandler.class); GlobalRunManager runManager = mock(GlobalRunManager.class); + CustomKarateEngineAdapter engine = mock(CustomKarateEngineAdapter.class); ScenarioRuntime sr = mock(ScenarioRuntime.class); TestResult testResult = mock(TestResult.class); @@ -31,7 +51,7 @@ class KarateHookTest { @BeforeEach void setUp() { - hook = new KarateHook(constructor, functionsHandler, runManager); + hook = new KarateHook(constructor, functionsHandler, runManager, engine); } @Test @@ -95,6 +115,154 @@ void shouldWrapExceptionWhenReportFails() { verify(functionsHandler).handleFacadeFunctions(sr); } + @Test + void shouldStartTimerWhenLogNextStepIsTrue() { + Step step = mock(Step.class); + when(step.getPrefix()).thenReturn("Given"); + + ScenarioRuntime sr = mock(ScenarioRuntime.class); + + Tags tags = mock(Tags.class); + when(tags.getTags()).thenReturn(Collections.emptyList()); + injectFinalField(sr, "tags", tags); + + KarateEngineAdapter engine = mock(KarateEngineAdapter.class); + when(engine.getVariable("log_next_step")).thenReturn(true); + + injectFinalField(hook, "engine", engine); + + try (MockedStatic timer = mockStatic(StepTimer.class)) { + boolean result = hook.beforeStep(step, sr); + + assertTrue(result); + + verify(engine).setVariable("log_next_step", false); + timer.verify(() -> StepTimer.start(any()), times(1)); + } + } + + @Test + void shouldStartTimerWhenLogStepsTagPresent() { + Step step = mock(Step.class); + when(step.getPrefix()).thenReturn("When"); + + ScenarioRuntime sr = mock(ScenarioRuntime.class); + + Tags tags = mock(Tags.class); + when(tags.getTags()).thenReturn(List.of("LogSteps")); + injectFinalField(sr, "tags", tags); + + KarateEngineAdapter engine = mock(KarateEngineAdapter.class); + when(engine.getVariable("log_next_step")).thenReturn(false); + + injectFinalField(hook, "engine", engine); + + try (MockedStatic timer = mockStatic(StepTimer.class)) { + + hook.beforeStep(step, sr); + + verify(engine).setVariable("log_next_step", false); + timer.verify(() -> StepTimer.start(any()), times(1)); + } + } + + @Test + void shouldNotStartTimerWhenStepIsNotDslAndNoFlags() { + Step step = mock(Step.class); + ScenarioRuntime sr = mock(ScenarioRuntime.class); + + when(step.getPrefix()).thenReturn("*"); + + ScenarioEngine engine = mock(ScenarioEngine.class); + + try (MockedStatic mocked = mockStatic(ScenarioEngine.class)) { + mocked.when(ScenarioEngine::get).thenReturn(engine); + + boolean result = hook.beforeStep(step, sr); + + assertTrue(result); + verify(engine, never()).setVariable(eq("log_next_step"), any()); + } + } + + @Test + void shouldSkipStepIfTimerNotStarted() { + StepResult result = mock(StepResult.class); + Step step = mock(Step.class); + ScenarioRuntime sr = mock(ScenarioRuntime.class); + + when(result.getStep()).thenReturn(step); + + try (MockedStatic timer = mockStatic(StepTimer.class)) { + timer.when(() -> StepTimer.stop(any())).thenReturn(-1L); + + hook.afterStep(result, sr); + + timer.verify(() -> StepTimer.stop(any())); + } + } + + @Test + void shouldStoreStepWhenTimerStopped() { + StepResult result = mock(StepResult.class); + Step step = mock(Step.class); + ScenarioRuntime sr = mock(ScenarioRuntime.class); + + when(result.getStep()).thenReturn(step); + when(step.getText()).thenReturn("Some step"); + + ScenarioEngine engine = mock(ScenarioEngine.class); + + try (MockedStatic mockedEngine = mockStatic(ScenarioEngine.class); + MockedStatic mockedTimer = mockStatic(StepTimer.class); + MockedStatic storage = mockStatic(StepStorage.class)) { + + mockedEngine.when(ScenarioEngine::get).thenReturn(engine); + mockedTimer.when(() -> StepTimer.stop(any())).thenReturn(100L); + + hook.afterStep(result, sr); + + storage.verify(() -> StepStorage.addStep(any(TestStep.class))); + } + } + + @Test + void shouldUseCustomStepTitle() { + + StepResult result = mock(StepResult.class); + Step step = mock(Step.class); + ScenarioRuntime sr = mock(ScenarioRuntime.class); + + when(result.getStep()).thenReturn(step); + when(step.getText()).thenReturn("Original"); + + KarateEngineAdapter engine = mock(KarateEngineAdapter.class); + + when(engine.getVariable("log_next_step_title")) + .thenReturn("Custom title"); + + try (MockedStatic timer = mockStatic(StepTimer.class); + MockedStatic storage = mockStatic(StepStorage.class)) { + + timer.when(() -> StepTimer.stop(any())).thenReturn(50L); + + KarateHook hook = new KarateHook( + constructor, + functionsHandler, + runManager, + engine + ); + + hook.afterStep(result, sr); + + storage.verify(() -> StepStorage.addStep( + argThat(s -> s.getStepTitle().equals("Custom title")) + )); + + verify(engine).setVariable("log_next_step_title", null); + } + } + static void injectFinalField(Object target, String fieldName, Object value) { try { Field field = target.getClass().getDeclaredField(fieldName); @@ -104,8 +272,4 @@ static void injectFinalField(Object target, String fieldName, Object value) { throw new RuntimeException(e); } } - - - } - diff --git a/java-reporter-karate/src/test/java/io/testomat/karate/marker/StepMarkerTest.java b/java-reporter-karate/src/test/java/io/testomat/karate/marker/StepMarkerTest.java new file mode 100644 index 00000000..a5a764e7 --- /dev/null +++ b/java-reporter-karate/src/test/java/io/testomat/karate/marker/StepMarkerTest.java @@ -0,0 +1,60 @@ +package io.testomat.karate.marker; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import com.intuit.karate.core.ScenarioEngine; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +class StepMarkerTest { + + private static final String LOG_NEXT_STEP = "log_next_step"; + private static final String LOG_NEXT_STEP_TITLE = "log_next_step_title"; + private static final String MY_STEP = "My step"; + private static final String TITLE = "title"; + + @Test + void shouldMarkNextStep() { + ScenarioEngine engine = mock(ScenarioEngine.class); + + try (MockedStatic mocked = mockStatic(ScenarioEngine.class)) { + mocked.when(ScenarioEngine::get).thenReturn(engine); + + StepMarker.mark(); + + verify(engine).setVariable(LOG_NEXT_STEP, true); + verifyNoMoreInteractions(engine); + } + } + + @Test + void shouldMarkNextStepWithTitle() { + ScenarioEngine engine = mock(ScenarioEngine.class); + + try (MockedStatic mocked = mockStatic(ScenarioEngine.class)) { + mocked.when(ScenarioEngine::get).thenReturn(engine); + + StepMarker.mark(MY_STEP); + + verify(engine).setVariable(LOG_NEXT_STEP, true); + verify(engine).setVariable(LOG_NEXT_STEP_TITLE, MY_STEP); + verifyNoMoreInteractions(engine); + } + } + + @Test + void shouldDoNothingIfEngineIsNull() { + try (MockedStatic mocked = mockStatic(ScenarioEngine.class)) { + mocked.when(ScenarioEngine::get).thenReturn(null); + + StepMarker.mark(); + StepMarker.mark(TITLE); + + mocked.verify(ScenarioEngine::get, times(2)); + } + } +} diff --git a/java-reporter-testng/pom.xml b/java-reporter-testng/pom.xml index f6db8543..9204f4a6 100644 --- a/java-reporter-testng/pom.xml +++ b/java-reporter-testng/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter-testng - 0.7.9 + 0.7.10 jar Testomat.io Java Reporter TestNG @@ -47,7 +47,7 @@ io.testomat java-reporter-core - 0.9.0 + 0.9.1 org.slf4j diff --git a/pom.xml b/pom.xml index 1c0d3a78..45e6c1eb 100644 --- a/pom.xml +++ b/pom.xml @@ -6,11 +6,11 @@ io.testomat java-reporter - 0.1.0 + 0.2.0 pom Testomat.io Java Reporter - Java reporter library for Testomat.io integration with JUnit 5, TestNG, and Cucumber support + Java reporter library for Testomat.io integration with JUnit 5, TestNG, Cucumber, and Karate support https://github.com/testomatio/java-reporter