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 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