From 8e45846195bd078aab11a480f01d355e8a18909c Mon Sep 17 00:00:00 2001 From: Timon Borter Date: Sun, 17 Mar 2024 21:37:56 +0100 Subject: [PATCH] fix(#1122): gracefully shutdown test context on error --- .../citrusframework/context/TestContext.java | 19 ++-- .../context/TestContextUnitTest.java | 87 +++++++++++++++++++ 2 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 core/citrus-api/src/test/java/org/citrusframework/context/TestContextUnitTest.java diff --git a/core/citrus-api/src/main/java/org/citrusframework/context/TestContext.java b/core/citrus-api/src/main/java/org/citrusframework/context/TestContext.java index e6683c3f24..a1ef85e74c 100644 --- a/core/citrus-api/src/main/java/org/citrusframework/context/TestContext.java +++ b/core/citrus-api/src/main/java/org/citrusframework/context/TestContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2010 the original author or authors. + * Copyright 2006-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,9 +74,7 @@ * for replacing dynamic content(variables and functions) in message payloads and headers. */ public class TestContext implements ReferenceResolverAware, TestActionListenerAware { - /** - * Logger - */ + private static final Logger logger = LoggerFactory.getLogger(TestContext.class); /** @@ -439,9 +437,13 @@ public CitrusRuntimeException handleError(String testName, String packageName, S CitrusRuntimeException exception = new CitrusRuntimeException(message, cause); // inform test listeners with failed test - testListeners.onTestStart(dummyTest); - testListeners.onTestFailure(dummyTest, exception); - testListeners.onTestFinish(dummyTest); + try { + testListeners.onTestStart(dummyTest); + testListeners.onTestFailure(dummyTest, exception); + testListeners.onTestFinish(dummyTest); + } catch (Exception e) { + logger.warn("Executing error handler listener failed!", e); + } return exception; } @@ -903,7 +905,8 @@ public boolean isSuccess(TestResult testResult) { /** * Empty test case implementation used as test result when tests fail before execution. */ - private static class EmptyTestCase implements TestCase { + static class EmptyTestCase implements TestCase { + private final String testName; private final String packageName; diff --git a/core/citrus-api/src/test/java/org/citrusframework/context/TestContextUnitTest.java b/core/citrus-api/src/test/java/org/citrusframework/context/TestContextUnitTest.java new file mode 100644 index 0000000000..3bb06f67c0 --- /dev/null +++ b/core/citrus-api/src/test/java/org/citrusframework/context/TestContextUnitTest.java @@ -0,0 +1,87 @@ +package org.citrusframework.context; + +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.report.TestListener; +import org.citrusframework.report.TestListeners; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class TestContextUnitTest { + + private TestContext fixture; + + @BeforeTest + void beforeTestClass() { + fixture = new TestContext(); + } + + @Test + public void handleErrorGracefullyHandlesErrorInTestStartListener() { + var testListenerMock = attachTestListenerMockToFixture(); + + var cause = new CitrusRuntimeException("thrown with a purpose!"); + doThrow(cause).when(testListenerMock).onTestStart(any(TestContext.EmptyTestCase.class)); + + invokeHandleErrorOnFixture(cause); + + verify(testListenerMock).onTestStart(any(TestContext.EmptyTestCase.class)); + verifyNoMoreInteractions(testListenerMock); + } + + @Test + public void handleErrorGracefullyHandlesErrorInTestFailureListener() { + var testListenerMock = attachTestListenerMockToFixture(); + + var cause = new CitrusRuntimeException("thrown with a purpose!"); + doThrow(cause).when(testListenerMock).onTestFailure(any(TestContext.EmptyTestCase.class), any(CitrusRuntimeException.class)); + + invokeHandleErrorOnFixture(cause); + + verify(testListenerMock).onTestStart(any(TestContext.EmptyTestCase.class)); + verify(testListenerMock).onTestFailure(any(TestContext.EmptyTestCase.class), any(CitrusRuntimeException.class)); + verifyNoMoreInteractions(testListenerMock); + } + + @Test + public void handleErrorGracefullyHandlesErrorInTestFinishListener() { + var testListenerMock = attachTestListenerMockToFixture(); + + var cause = new CitrusRuntimeException("thrown with a purpose!"); + doThrow(cause).when(testListenerMock).onTestFinish(any(TestContext.EmptyTestCase.class)); + + invokeHandleErrorOnFixture(cause); + + verify(testListenerMock).onTestStart(any(TestContext.EmptyTestCase.class)); + verify(testListenerMock).onTestFailure(any(TestContext.EmptyTestCase.class), any(CitrusRuntimeException.class)); + verify(testListenerMock).onTestFinish(any(TestContext.EmptyTestCase.class)); + } + + private TestListener attachTestListenerMockToFixture() { + var testListeners = new TestListeners(); + + var testListenerMock = mock(TestListener.class); + testListeners.addTestListener(testListenerMock); + + fixture.setTestListeners(testListeners); + + return testListenerMock; + } + + private void invokeHandleErrorOnFixture(CitrusRuntimeException cause) { + var testName = "test name"; + var packageName = "package name"; + + var message = "additional message"; + + var citrusRuntimeException = fixture.handleError(testName, packageName, message, cause); + assertEquals(citrusRuntimeException.getMessage(), message); + assertEquals(citrusRuntimeException.getCause(), cause); + } +}