When important data is written to STDIN/STDOUT and an application relies on specific system properties or environment variables, writing tests is getting more complicated.

System Rules is a collection of JUnit rules that helps us writing Java tests for everything that deals with java.lang.System.

In the following short examples I’d like to demonstrate how to deal with system properties, environment variables, STDOUT and STDERR and capturing both for testing e.g. for some golden master refactoring.

gradle running junit tests
Figure 1. Running JUnit with Gradle

Gradle Dependencies

Using Gradle here, this is our build.gradle – we’re adding the dependencies for JUnit, Hamcrest-Matchers and of course system-rules.

In addition, we’re configuring the test stage to give some more detailed output and to print STDOUT and STDERR run from the tests to the console.

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.12'
    testCompile 'com.github.stefanbirkner:system-rules:1.16.0'
    testCompile 'org.hamcrest:hamcrest-all:1.3'
}

test {
    testLogging {
        testLogging.showStandardStreams = true
        events "passed", "skipped", "failed", "standardOut", "standardError"
    }
}

Using System Rules

Now we’re ready to write some test ..

Providing System Properties

System properties may be provided using the ProvideSystemProperty rule as shown in the following example:

package com.hascode.tutorial;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ProvideSystemProperty;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ProvidingSystemPropertiesTest {
    @Rule
    public final ProvideSystemProperty provideProperty = new ProvideSystemProperty("xxx", "yyy");

    @Test
    public void _1_shouldProvideProperty() throws Exception {
        assertThat(System.getProperty("xxx"), is("yyy"));
        System.setProperty("xxx", "zzz");
        assertThat(System.getProperty("xxx"), is("zzz"));
    }

    @Test
    public void _2_shouldHaveResettedProperty() throws Exception {
        assertThat(System.getProperty("xxx"), is("yyy"));
    }
}

Clearing / Resetting System Properties

System properties may be resetted after each test run using the ClearSystemProperties rule.

package com.hascode.tutorial;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;

import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ClearSystemProperties;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ClearingSystemPropertiesTest {
    @Rule
    public final ClearSystemProperties xxxPropertyCleared = new ClearSystemProperties("xxx");

    @Test
    public void _1_shouldSetProperty() throws Exception {
        assertThat(System.getProperty("xxx"), is(nullValue()));
        System.setProperty("xxx", "yyy");
        assertThat(System.getProperty("xxx"), notNullValue());
    }

    @Test
    public void _2_shouldHaveClearedProperty() {
        assertThat(System.getProperty("xxx"), is(nullValue()));
    }

}

Fetching System.in and System.out

This is our class under test, printing to STDOUT and STDERR.

package com.hascode.tutorial;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class ConsoleTimePrinter {
    public void printToStdtOut(String formatPattern) {
        ZonedDateTime zdt = ZonedDateTime.now();
        try {
            System.out.println(zdt.format(DateTimeFormatter.ofPattern(formatPattern)));
        } catch (IllegalArgumentException e) {
            System.err.printf("invalid pattern given: %s, error-message: %s\n", formatPattern, e.getMessage());
        }
    }
}

To capture the output from STDOUT and STDERR, we may use the SystemOutRule and SystemErrRule:

package com.hascode.tutorial;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemErrRule;
import org.junit.contrib.java.lang.system.SystemOutRule;

public class ConsoleTimePrinterTest {
    ConsoleTimePrinter underTest = new ConsoleTimePrinter();

    @Rule
    public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();

    @Rule
    public final SystemErrRule systemErrRule = new SystemErrRule().enableLog();

    @Test
    public void shouldPrintValidFormattedDateToStdOut() {
        underTest.printToStdtOut("yyyy");
        assertThat(systemOutRule.getLog(), equalTo("2016\n"));
    }

    @Test
    public void shouldPrintInValidFormatWarningToStdErr() {
        underTest.printToStdtOut("XXXXXXX");
        assertThat(systemErrRule.getLog(),
                equalTo("invalid pattern given: XXXXXXX, error-message: Too many pattern letters: X\n"));
    }

}

Providing Environment Variables

We may provide environment variables to our tests using the EnvironmentVariables rule:

package com.hascode.tutorial;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.EnvironmentVariables;

public class EnvironmentVariablesTest {
    @Rule
    public final EnvironmentVariables envVars = new EnvironmentVariables();

    @Test
    public void shouldSetAndReadEnvironmentVariables() throws Exception {
        envVars.set("JAVA_HOME", "/tmp");
        assertThat(System.getenv("JAVA_HOME"), equalTo("/tmp"));
    }
}

Disallowing System.exit

We may configure if System.exit is allowed using the ExpectedSystemExit rule:

package com.hascode.tutorial;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;

public class DisallowingSystemExitTest {

    @Rule
    public final ExpectedSystemExit systemExitRule = ExpectedSystemExit.none();

    @Test
    public void shouldFailWhenCallingSystemExit() throws Exception {
        systemExitRule.expectSystemExitWithStatus(1);
        System.exit(1);
    }
}

Disallowing Writing to System.out

We may configure if writing to STDOUT is allowed using the DisallowWriteToSystemOut rule.

package com.hascode.tutorial;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.DisallowWriteToSystemOut;

public class DisallowingSystemOutTest {

    @Rule
    public final DisallowWriteToSystemOut disallowSysoutWrite = new DisallowWriteToSystemOut();

    @Test(expected = AssertionError.class)
    public void shouldFailWhenWritingToSysout() throws Exception {
        System.out.println("i fail");
    }
}

Running the Tests

Run with gradle like this

$ gradle clean test
[..]

com.hascode.tutorial.DisallowingSystemExitTest > shouldFailWhenCallingSystemExit PASSED

com.hascode.tutorial.ConsoleTimePrinterTest > shouldPrintValidFormattedDateToStdOut STANDARD_OUT
    2016

com.hascode.tutorial.ConsoleTimePrinterTest > shouldPrintValidFormattedDateToStdOut PASSED

com.hascode.tutorial.ConsoleTimePrinterTest > shouldPrintInValidFormatWarningToStdErr STANDARD_ERROR
    invalid pattern given:
    XXXXXXX
    , error-message:
    Too many pattern letters: X

com.hascode.tutorial.ConsoleTimePrinterTest > shouldPrintInValidFormatWarningToStdErr PASSED

com.hascode.tutorial.ClearingSystemPropertiesTest > _1_shouldSetProperty PASSED

com.hascode.tutorial.ClearingSystemPropertiesTest > _2_shouldHaveClearedProperty PASSED

com.hascode.tutorial.EnvironmentVariablesTest > shouldSetAndReadEnvironmentVariables PASSED

com.hascode.tutorial.ProvidingSystemPropertiesTest > _1_shouldProvideProperty PASSED

com.hascode.tutorial.ProvidingSystemPropertiesTest > _2_shouldHaveResettedProperty PASSED

com.hascode.tutorial.DisallowingSystemOutTest > shouldFailWhenWritingToSysout PASSED

BUILD SUCCESSFUL

Total time: 3.853 secs

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://github.com/hascode/systemrules-junit-tutorial.git