Handling System Properties, Environment Variables, STDOUT/STDERR in JUnit Tests with System Rules
December 19th, 2016 by Micha KopsWhen 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.
Contents
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 Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/systemrules-junit-tutorial.git
Resources
Tags: environment, gradle, junit, maven, properties, system, system.in, system.out, tdd, testrule