20

We are using an approach similar to System Rules to handle (system) properties in our JUnit 4 tests. The main reason for this is to clean up the environment after each test, so that other tests do not inadvertently depend on possible side effects.

Since JUnit 5 is released, I wonder if there is a "JUnit 5 way" of doing this?

beatngu13
  • 7,201
  • 6
  • 37
  • 66
  • JUnit5 in general nowadays has one answer for everything, it looks like - [extension API](http://junit.org/junit5/docs/current/user-guide/#extensions). I haven't played with it myself, but most things I think can be expressed with it. That said, JUnit4 way should still be perfectly viable for what you want. – M. Prokhorov Oct 20 '17 at 10:15
  • @M.Prokhorov thanks for the hint, will have a look at the extension model. – beatngu13 Oct 20 '17 at 10:55

3 Answers3

27

There is JUnit Pioneer, a "JUnit 5 extension pack". It comes with @ClearSystemProperty and @SetSystemProperty. From the docs:

The @ClearSystemProperty and @SetSystemProperty annotations can be used to clear, respectively, set the values of system properties for a test execution. Both annotations work on the test method and class level, are repeatable as well as combinable. After the annotated method has been executed, the properties mentioned in the annotation will be restored to their original value or will be cleared if they didn't have one before. Other system properties that are changed during the test, are not restored.

Example:

@Test
@ClearSystemProperty(key = "some key")
@SetSystemProperty(key = "another key", value = "new value")
void test() {
    assertNull(System.getProperty("some key"));
    assertEquals("new value", System.getProperty("another key"));
}
beatngu13
  • 7,201
  • 6
  • 37
  • 66
  • It does not work with Java 17 due to restrictions with the system module. – Anatoly Yakimchuk Nov 02 '22 at 17:25
  • @AnatolyYakimchuk could it be you are confusing system properties and environment variables? The former shouldn't yield any warnings/errors, for the latter there is [this section](https://junit-pioneer.org/docs/environment-variables/#warnings-for-reflective-access) in the Pioneer docs. – beatngu13 Nov 14 '22 at 20:13
  • I ended up using System Stubs library https://github.com/webcompere/system-stubs it does correctly set env variables even with the new (17+) version of Java. – Anatoly Yakimchuk Nov 14 '22 at 22:33
13

You can use the extension API. You could create an annotation which defines your extension to a test method.

import org.junit.jupiter.api.extension.ExtendWith;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ExtendWith(SystemPropertyExtension.class)
public @interface SystemProperty {

    String key();

    String value();
}

Then, you can create the extension class:

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class SystemPropertyExtension implements AfterEachCallback, BeforeEachCallback {

    @Override
    public void afterEach(ExtensionContext extensionContext) throws Exception {
        SystemProperty annotation = extensionContext.getTestMethod().get().getAnnotation(SystemProperty.class);
        System.clearProperty(annotation.key());
    }

    @Override
    public void beforeEach(ExtensionContext extensionContext) throws Exception {
        SystemProperty annotation = extensionContext.getTestMethod().get().getAnnotation(SystemProperty.class);
        System.setProperty(annotation.key(), annotation.value());
    }
}

Finally, you can annotate your test with properties:

@Test
@SystemProperty(key = "key", value = "value")
void testPropertey() {
    System.out.println(System.getProperty("key"));
}

This solution supports only one system property for each test. If you want to support multiple test, you could use a nested annotation and the extension could handle this as well:

@Test
@SystemProperties({
    @SystemProperty(key = "key1", value = "value"),
    @SystemProperty(key = "key2", value = "value")
})
void testPropertey() {
    System.out.println(System.getProperty("key1"));
    System.out.println(System.getProperty("key2"));
}
schrieveslaach
  • 1,689
  • 1
  • 15
  • 32
  • 3
    It's JUnit5, so it is Java8, and so it has repeatable annotations, and there's really no need to expose `@SystemProperties` annotation to user - just mark `@SystemProperty` as repeatable and call `getAnnotationS` instead of `getAnnotation`. – M. Prokhorov Oct 23 '17 at 11:27
  • 1
    will not work for by hitting the System.getenv() from the code,, – ses May 01 '18 at 21:34
  • 1
    @ses system properties != environment variables. If you have to deal with the latter, check out this answer: https://stackoverflow.com/a/63494695/3429133 – beatngu13 Aug 19 '20 at 20:47
3

The JUnit Pioneer way requires the system properties to be known at compile time. Where they are generated at runtime, say via Testcontainers or Wiremock creating things on random ports, it may be better to use something which can be driven from dynamic values.

The problem can be solved with System Stubs https://github.com/webcompere/system-stubs which provides JUnit 5 and is a fork of the code from System Lambda, itself built by the author of System Rules.

@ExtendWith(SystemStubsExtension.class)
class SomeTest {
    // can be initialised here with some up front properties
    // or leave like this for auto initialization
    @SystemStub
    private SystemProperties someProperties;

    @BeforeEach
    void beforeEach() {
        someProperties.set("prop1", "value1")
            .set("prop2", "value2");
    }

    @Test
    void someTest() {
        // properties are set here
        // and can also call System.setProperty


        // properties reset to state before the test case ran
        // as the test case is tidied up
    }
}

Ashley Frieze
  • 4,993
  • 2
  • 29
  • 23
  • One can use Pioneer's `@ClearSystemProperty` and then `System#setProperty(...)` for non-constant expressions, so values don't need to be known at compile time (see [docs](https://junit-pioneer.org/docs/system-properties/)). – beatngu13 Dec 04 '20 at 21:25
  • True. As `System.setProperty` is a pretty easy thing to use, it's not a big deal per-se. However, what's nice about the above is that the set of System properties can be defined in the `SystemProperties` object at construction, or in the `Before` method, and is not yet applied to the properties until the test runs. Even better is the `EnvironmentVariables` alternative from SystemStubs, which does the same for the harder-to-reach environment variables. – Ashley Frieze Dec 07 '20 at 18:00