
de.weltraumschaf.commons.testing.CapturedOutput Maven / Gradle / Ivy
/*
* LICENSE
*
* "THE BEER-WARE LICENSE" (Revision 43):
* "Sven Strittmatter" wrote this file.
* As long as you retain this notice you can do whatever you want with
* this stuff. If we meet some day, and you think this stuff is worth it,
* you can buy me a non alcohol-free beer in return.
*
* Copyright (C) 2012 "Sven Strittmatter"
*/
package de.weltraumschaf.commons.testing;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import org.hamcrest.Matcher;
import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* This rule captures all output written to {@link System#out} and {@link System#err}.
*
*
* This rule redirects the constant print streams for out/err to an {@link CapturingPrintStream} before each test and
* backups the origin streams after each test.
*
*
* {@code
* public class OutputTest {
*
* @Rule
* public final CapturedOutput output = new CapturedOutput();
*
* @Test
* public void captureOut() {
* output.expectOut("foobar");
* output.expectOut(not("snafu"));
*
* System.out.print("foobar");
* }
*
* @Test
* public void captureErr() {
* output.expectErr("foobar");
* output.expectErr(not("snafu"));
*
* System.err.print("foobar");
* }
*
* }
* }
*
* @since 1.0.0
* @author Sven Strittmatter
*/
public final class CapturedOutput implements TestRule {
/**
* Build the matchers for STDOUT expectations.
*/
private final CapturedOutputMatcherBuilder outMatcherBuilder = new CapturedOutputMatcherBuilder();
/**
* Build the matchers for STDERR expectations.
*/
private final CapturedOutputMatcherBuilder errMatcherBuilder = new CapturedOutputMatcherBuilder();
/**
* Captures the data written to STDOUT.
*/
private final CapturingPrintStream out;
/**
* Captures the data written to STDERR.
*/
private final CapturingPrintStream err;
/**
* Holds the original STDOUT from before test method.
*/
private PrintStream outBackup;
/**
* Holds the original STDERR from before test method.
*/
private PrintStream errBackup;
/**
* Creates a capturing rule with platform encoding.
*
* @throws UnsupportedEncodingException if the platform encoding is not supported
*/
public CapturedOutput() throws UnsupportedEncodingException {
this(Charset.defaultCharset().name());
}
/**
* Dedicated constructor.
*
* @param encoding must not be {@code null} or empty
* @throws UnsupportedEncodingException if the platform encoding is not supported
*/
public CapturedOutput(final String encoding) throws UnsupportedEncodingException {
out = new CapturingPrintStream(encoding);
err = new CapturingPrintStream(encoding);
}
/**
* Adds to the list of requirements for any output printed to STDOUT that it should contain string
* {@code substring}.
*
* @param substring must not be {@code null}
*/
public void expectOut(final String substring) {
expectOut(containsString(notNull(substring, "substring")));
}
/**
* Adds to the list of requirements for any output printed to STDOUT.
*
* @param matcher must not be {@code null}
*/
public void expectOut(final Matcher matcher) {
outMatcherBuilder.add(notNull(matcher, "matcher"));
}
/**
* Adds to the list of requirements for any output printed to STDERR that it should contain string
* {@code substring}.
*
* @param substring must not be {@code null}
*/
public void expectErr(final String substring) {
expectErr(containsString(notNull(substring, "substring")));
}
/**
* Adds to the list of requirements for any output printed to STDERR.
*
* @param matcher must not be {@code null}
*/
public void expectErr(final Matcher matcher) {
errMatcherBuilder.add(notNull(matcher, "matcher"));
}
@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
// CHECKSTYLE:OFF
// Throwable required by API.
public void evaluate() throws Throwable {
// CHECKSTYLE:ON
redirectOutputStreams();
try {
base.evaluate();
assertCapturedOut();
assertCapturedErr();
} finally {
restoreOutputStreams();
}
}
};
}
/**
* Set and backup STDERR/STDOUT print streams.
*/
private void redirectOutputStreams() {
outBackup = System.out;
System.setOut(out.reset());
errBackup = System.err;
System.setErr(err.reset());
}
/**
* Applies matchers on captured error output if there are any matchers.
*
* @throws UnsupportedEncodingException if the platform encoding is not supported
*/
private void assertCapturedErr() throws UnsupportedEncodingException {
if (errMatcherBuilder.expectsSomething()) {
assertThat(err.getCapturedOutput(), errMatcherBuilder.build());
}
}
/**
* Applies matchers on captured standard output if there are any matchers.
*
* @throws UnsupportedEncodingException if the platform encoding is not supported
*/
private void assertCapturedOut() throws UnsupportedEncodingException {
if (outMatcherBuilder.expectsSomething()) {
assertThat(out.getCapturedOutput(), outMatcherBuilder.build());
}
}
/**
* Restore STDERR/STDOUT print streams.
*/
private void restoreOutputStreams() {
System.setOut(outBackup);
System.setErr(errBackup);
}
/**
* Validates that given subject is not {@code null}.
*
*
* Will throw {@link NullPointerException} if subject is {@code null}.
*
*
* @param type of subject
* @param subject tested subject
* @param description name of tested subject for exception message
* @return the subject, if not {@code null}
*/
private static T notNull(final T subject, final String description) {
if (null == subject) {
throw new NullPointerException(String.format("Parameter '%s' must not be null!", description));
}
return subject;
}
/**
* Builds string matchers.
*/
private static final class CapturedOutputMatcherBuilder {
/**
* Hold all matchers.
*/
private final List> matchers = new ArrayList>();
/**
* Adds a matcher.
*
* @param matcher must not be {@code null}
*/
void add(final Matcher matcher) {
matchers.add(matcher);
}
/**
* Whether the builder has any matcher.
*
* @return {@code true} if there are matchers, else {@code false}
*/
boolean expectsSomething() {
return !matchers.isEmpty();
}
/**
* Returns the combined matcher.
*
* @return never {@code null}
*/
Matcher build() {
if (matchers.size() == 1) {
return matchers.get(0);
}
return allOf(castedMatchers());
}
/**
* Casts list to matchers of super type string.
*
* @return new instance, not {@code null}
*/
@SuppressWarnings({"unchecked", "rawtypes" })
private List> castedMatchers() {
return new ArrayList>((List) matchers);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy