@@ -119,6 +120,13 @@
provided
+
+
+ org.testng
+ testng
+ 7.7.0
+ provided
+
diff --git a/system-stubs-testng/README.md b/system-stubs-testng/README.md
new file mode 100644
index 0000000..c17fd0e
--- /dev/null
+++ b/system-stubs-testng/README.md
@@ -0,0 +1,101 @@
+# System Stubs TestNG
+
+Provides some automatic instantiation of System Stubs objects during the test lifecycle.
+
+```xml
+
+ uk.org.webcompere
+ system-stubs-testng
+ 2.1.1
+
+```
+
+## Options
+
+### Stub Without the Plugin
+System Stubs Core can be used with TestNG as it is framework agnostic.
+
+We can call the `setup` method on any of the stubs in a before method, and `teardown` in the after method.
+
+```java
+private EnvironmentVariables environmentVariables = new EnvironmentVariables();
+
+@BeforeTest
+public void beforeTest() throws Exception {
+ environmentVariables.set("setinbefore", "yes");
+
+ environmentVariables.setup();
+}
+
+@AfterTest
+public void afterTest() throws Exception {
+ environmentVariables.teardown();
+}
+```
+
+With this code, we'd expect tests to be able to modify the runtime environment by manipulating the
+`environmentVariables` object, and we'd expect the tests to have an environment variable `setinbefore` set
+to `yes`.
+
+Similarly, we can use `setup` and `teardown` inside a test case, or use the `SystemStubs` methods such as
+`withEnvironmentVariables`. See the [main documentation](../README.md) for more on the execute around pattern.
+
+### Using of the Plugin
+
+The plugin:
+
+- Automatically instantiates system stubs objects before they're first used by a TestNG annotated method
+- Activates the objects during tests
+- Turns the objects off after tests
+
+Usage:
+
+```java
+@Listeners(SystemStubsListener.class)
+public class CaptureSystemOutTest {
+
+ @SystemStub
+ private SystemOut out;
+
+ @BeforeTest
+ public void beforeTest() {
+ out.clear();
+ }
+
+ @Test
+ public void canReadThingsSentToSystemOut() {
+ // simulate the system under test writing to std out
+ System.out.println("Can I assert this?");
+
+ assertThat(out.getText()).isEqualTo("Can I assert this?\n");
+ }
+}
+```
+
+> Note: in this instance we've used the `SystemOut` stub. We've had to remember to call its `clear` method as it
+> will be shared between tests.
+
+We can use each of the stubs such as:
+
+- `EnvironmentVariables` - for overriding the environment variables
+- `SystemProperties` - for temporarily overwriting system properties and then restoring them afterwards
+- `SystemOut` - for tapping the `System.out`
+- ... and the others
+
+All we need to do is:
+
+- Add the `@Listeners(SystemStubsListener.class)` annotation to our TestNG test class (using an array with {} if we have other listeners)
+- Add a field for each System Stub we want to use
+- Annotate that field with the `@SystemStubs` annotation
+
+### Benefits of the Plugin
+
+With the plugin, there's less boilerplate to write. Any exception handling is also covered by the plugin - or at
+least, we don't have to explicitly add `throws` to any of our methods that set up or teardown a stub.
+
+However, the plugin is simple and opinionated. For fine-grained control of the stubs, the direct method
+may sometimes be preferable.
+
+## Feedback
+
+This TestNG module is incubating. Please raise issues with examples if it proves to have issues in practice.
diff --git a/system-stubs-testng/pom.xml b/system-stubs-testng/pom.xml
new file mode 100644
index 0000000..2773153
--- /dev/null
+++ b/system-stubs-testng/pom.xml
@@ -0,0 +1,83 @@
+
+
+ 4.0.0
+
+
+ uk.org.webcompere
+ system-stubs-parent
+ 2.1.2-SNAPSHOT
+ ../pom.xml
+
+
+
+ system-stubs-testng
+ 2.1.2-SNAPSHOT
+ jar
+
+
+ 11
+ 11
+
+
+
+
+ uk.org.webcompere
+ system-stubs-core
+
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+
+
+
+ org.testng
+ testng
+ provided
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+
+
+
diff --git a/system-stubs-testng/src/main/java/uk/org/webcompere/systemstubs/testng/SystemStub.java b/system-stubs-testng/src/main/java/uk/org/webcompere/systemstubs/testng/SystemStub.java
new file mode 100644
index 0000000..633abb0
--- /dev/null
+++ b/system-stubs-testng/src/main/java/uk/org/webcompere/systemstubs/testng/SystemStub.java
@@ -0,0 +1,16 @@
+package uk.org.webcompere.systemstubs.testng;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a field in a test class as a system stub - this causes the {@link SystemStubsListener} to activate
+ * it during tests. It also causes the field to become instantiated if left uninitialized
+ * @since 1.0.0
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SystemStub {
+}
diff --git a/system-stubs-testng/src/main/java/uk/org/webcompere/systemstubs/testng/SystemStubsListener.java b/system-stubs-testng/src/main/java/uk/org/webcompere/systemstubs/testng/SystemStubsListener.java
new file mode 100644
index 0000000..0352ef8
--- /dev/null
+++ b/system-stubs-testng/src/main/java/uk/org/webcompere/systemstubs/testng/SystemStubsListener.java
@@ -0,0 +1,119 @@
+package uk.org.webcompere.systemstubs.testng;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+import uk.org.webcompere.systemstubs.resource.TestResource;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.stream.Collectors.toList;
+import static uk.org.webcompere.systemstubs.resource.Resources.executeCleanup;
+
+/**
+ * Add this to a test class with:
+ *
+ *
+ * @@Listeners(SystemStubsListener.class)
+ * public class MyTestClass {
+ *
+ * }
+ *
+ * This causes any of the system stubs objects, that inherit {@link TestResource} to
+ * become active during tests. It will also instantiate any objects not initialized in the
+ * initializer list.
+ */
+public class SystemStubsListener implements IInvokedMethodListener {
+ @Override
+ public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+ List stubs = ensureAllStubsAreInstantiated(method);
+ if (method.isTestMethod()) {
+ try {
+ for (TestResource stub: stubs) {
+ stub.setup();
+ }
+ } catch (Exception e) {
+ throw new AssertionError("Could not set up stubs", e);
+ }
+ }
+ }
+
+ @Override
+ public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+ if (method.isTestMethod()) {
+ try {
+ executeCleanup(getAllStubs(method));
+ } catch (Exception e) {
+ throw new AssertionError("Could not tidy up stubs", e);
+ }
+ }
+ }
+
+ private static TestResource readSystemStubResource(Field field, Object testObject) {
+ if (!TestResource.class.isAssignableFrom(field.getType())) {
+ throw new IllegalArgumentException("Cannot use @SystemStub with non TestResource object in field " +
+ field.getName() + " this one's a " +
+ field.getType().getCanonicalName());
+ }
+ try {
+ makeAccessible(field);
+ return (TestResource)field.get(testObject);
+ } catch (Exception e) {
+ throw new AssertionError("Cannot read field " + field.getName(), e);
+ }
+ }
+
+ private static List getAllStubs(IInvokedMethod method) {
+ var testObject = method.getTestMethod().getInstance();
+ var fields = testObject.getClass().getDeclaredFields();
+ return Arrays.stream(fields)
+ .filter(field -> field.isAnnotationPresent(SystemStub.class))
+ .map(field -> readSystemStubResource(field, testObject))
+ .filter(Objects::nonNull)
+ .collect(toList());
+ }
+
+ private static T makeAccessible(T object) {
+ if (!object.isAccessible()) {
+ object.setAccessible(true);
+ }
+ return object;
+ }
+
+ private static List ensureAllStubsAreInstantiated(IInvokedMethod method) {
+ var testObject = method.getTestMethod().getInstance();
+ var fields = testObject.getClass().getDeclaredFields();
+ return Arrays.stream(fields)
+ .filter(field -> field.isAnnotationPresent(SystemStub.class))
+ .map(field -> instantiateIfNecessary(field, testObject))
+ .collect(toList());
+ }
+
+ @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION",
+ justification = "Generic catch block provided as lots can go wrong when using reflection")
+ private static TestResource instantiateIfNecessary(Field field, Object testObject) {
+ if (!TestResource.class.isAssignableFrom(field.getType())) {
+ throw new IllegalArgumentException("Cannot use @SystemStub with non TestResource object in field " +
+ field.getName() + " this one's a " +
+ field.getType().getCanonicalName());
+ }
+
+ try {
+ makeAccessible(field);
+ var currentObject = field.get(testObject);
+ if (currentObject == null) {
+ var newInstance = field.getType().getDeclaredConstructor().newInstance();
+ field.set(testObject, newInstance);
+ return (TestResource) newInstance;
+ }
+ return (TestResource) currentObject;
+ } catch (Exception e) {
+ throw new AssertionError("Cannot access field " + field.getName(), e);
+ }
+ }
+}
diff --git a/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/SystemStubsPluginWhenStubIsBlankTest.java b/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/SystemStubsPluginWhenStubIsBlankTest.java
new file mode 100644
index 0000000..6c9c34f
--- /dev/null
+++ b/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/SystemStubsPluginWhenStubIsBlankTest.java
@@ -0,0 +1,33 @@
+package uk.org.webcompere.systemstubs.testng;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Listeners(SystemStubsListener.class)
+public class SystemStubsPluginWhenStubIsBlankTest {
+
+ @SystemStub
+ private EnvironmentVariables environmentVariables;
+
+ @BeforeTest
+ public void beforeTest() {
+ // even though the stub looks to be null, it's instantiated by here
+ environmentVariables.set("setinbefore", "yes");
+ }
+
+ @Test
+ public void noEnvironmentVariable() {
+ assertThat(System.getenv("scooby")).isBlank();
+ }
+
+ @Test
+ public void hasEnvironmentVariable() {
+ environmentVariables.set("foo", "bar");
+
+ assertThat(System.getenv("foo")).isEqualTo("bar");
+ }
+}
diff --git a/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/SystemStubsPluginWhenStubIsDefinedTest.java b/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/SystemStubsPluginWhenStubIsDefinedTest.java
new file mode 100644
index 0000000..9ee5714
--- /dev/null
+++ b/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/SystemStubsPluginWhenStubIsDefinedTest.java
@@ -0,0 +1,39 @@
+package uk.org.webcompere.systemstubs.testng;
+
+import org.testng.IInvokedMethod;
+import org.testng.annotations.*;
+import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Listeners(SystemStubsListener.class)
+public class SystemStubsPluginWhenStubIsDefinedTest {
+
+ @SystemStub
+ private EnvironmentVariables environmentVariables = new EnvironmentVariables();
+
+ @BeforeTest
+ public void beforeTest() {
+ environmentVariables.set("setinbefore", "yes");
+
+ // shouldn't apply yet, as we're not inside a test
+ assertThat(System.getenv("setinbefore")).isNotEqualTo("yes");
+ }
+
+ @Test
+ public void noEnvironmentVariable() {
+ assertThat(System.getenv("scooby")).isBlank();
+ }
+
+ @Test
+ public void hasEnvironmentVariable() {
+ environmentVariables.set("foo", "bar");
+
+ assertThat(System.getenv("foo")).isEqualTo("bar");
+ }
+
+ @Test
+ public void environmentSetInBeforeWillApplyInTest() {
+ assertThat(System.getenv("setinbefore")).isEqualTo("yes");
+ }
+}
diff --git a/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/examples/CaptureSystemOutTest.java b/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/examples/CaptureSystemOutTest.java
new file mode 100644
index 0000000..474e341
--- /dev/null
+++ b/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/examples/CaptureSystemOutTest.java
@@ -0,0 +1,30 @@
+package uk.org.webcompere.systemstubs.testng.examples;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+import uk.org.webcompere.systemstubs.stream.SystemOut;
+import uk.org.webcompere.systemstubs.testng.SystemStub;
+import uk.org.webcompere.systemstubs.testng.SystemStubsListener;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Listeners(SystemStubsListener.class)
+public class CaptureSystemOutTest {
+
+ @SystemStub
+ private SystemOut out;
+
+ @BeforeTest
+ public void beforeTest() {
+ out.clear();
+ }
+
+ @Test
+ public void canReadThingsSentToSystemOut() {
+ // simulate the system under test writing to std out
+ System.out.println("Can I assert this?");
+
+ assertThat(out.getText()).startsWith("Can I assert this?");
+ }
+}
diff --git a/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/examples/SystemStubsWithoutPluginTest.java b/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/examples/SystemStubsWithoutPluginTest.java
new file mode 100644
index 0000000..6be3e36
--- /dev/null
+++ b/system-stubs-testng/src/test/java/uk/org/webcompere/systemstubs/testng/examples/SystemStubsWithoutPluginTest.java
@@ -0,0 +1,45 @@
+package uk.org.webcompere.systemstubs.testng.examples;
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
+import uk.org.webcompere.systemstubs.testng.SystemStub;
+import uk.org.webcompere.systemstubs.testng.SystemStubsListener;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SystemStubsWithoutPluginTest {
+
+ private EnvironmentVariables environmentVariables = new EnvironmentVariables();
+
+ @BeforeTest
+ public void beforeTest() throws Exception {
+ environmentVariables.set("setinbefore", "yes");
+
+ environmentVariables.setup();
+ }
+
+ @AfterTest
+ public void afterTest() throws Exception {
+ environmentVariables.teardown();
+ }
+
+ @Test
+ public void noEnvironmentVariable() {
+ assertThat(System.getenv("scooby")).isBlank();
+ }
+
+ @Test
+ public void hasEnvironmentVariable() {
+ environmentVariables.set("foo", "bar");
+
+ assertThat(System.getenv("foo")).isEqualTo("bar");
+ }
+
+ @Test
+ public void environmentSetInBeforeWillApplyInTest() {
+ assertThat(System.getenv("setinbefore")).isEqualTo("yes");
+ }
+}