15

I am transitioning from junit4 to junit5 on a project and trying to figure out how to test logs. Previously, I used

@Rule
OutputCapture outputCapture = new OutputCapture();

and would then write an assertion using outputCapture.toString(), for example

assertThat(outputCapture.toString(),containsString("status=200"));

Since @Rule annotation hasn't been implemented yet in junit5, I can't use outputCapture. Any ideas what to do instead? Thanks!

ByteByByte
  • 303
  • 1
  • 2
  • 11

4 Answers4

15

There's an extension provided for the same and you can use it as below:

@ExtendWith(OutputCaptureExtension.class)
public class MyTestClass {

    @Test
    void myTestCase(CapturedOutput capturedOutput) {
        assertTrue(capturedOutput.getOut().contains("expected string"));
        assertTrue(capturedOutput.getErr().contains("expected string"));
    }
}
VaibS
  • 1,627
  • 1
  • 15
  • 21
  • 2
    import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; – AnthonyW Sep 27 '22 at 13:25
6

We stumbled upon the same issue during our JUnit5 migration. After some research, I found a technical solution but it didn't seem like someone made a testing library out of it just yet. So that's what I did. It's published to Maven Central so you can use it right away:

https://github.com/netmikey/logunit

You can use it as follows:

public class MyModuleTest {

    @RegisterExtension
    LogCapturer logs = LogCapturer.create().captureForType(MyModule.class);

    @Test
    public void testLogging() {
        // ... do the testing ...

        // Run assertions on the logged messages
        logs.assertContains("Some message");
    }
}

(see the project's README for more examples)

netmikey
  • 2,422
  • 2
  • 28
  • 35
2

The Migration Tip from the JUnit5 documentation clearly states that -

@Rule and @ClassRule no longer exist; superseded by @ExtendWith; see the following section for partial rule support.

For the purpose of using the existing @Rule support from JUnit 4, there is though a way suggested for method or class level annotations.

As in JUnit 4, Rule-annotated fields as well as methods are supported. By using these class-level extensions on a test class such Rule implementations in legacy codebases can be left unchanged including the JUnit 4 rule import statements.

This limited form of Rule support can be switched on by the class-level annotation org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport


A better option would still be redesigning your test suite to use the Extension Model from JUnit5 if you're using it.

Naman
  • 27,789
  • 26
  • 218
  • 353
2

You can also easily test the log output written to System.out by quickly implementing a solution on your own as follows:

// Configure System.out to be written to a buffer from which we can read
PrintStream realSysOut = System.out;
BufferedOutputStream sysOutBuffer = new ByteArrayOutputStream();
System.setOut(new PrintStream(sysOutBuffer));
...
// Perform some action which logs something to System.out
System.out.println("Some random content written to System.out");
...
// Assert that a given string was written in the meantime to System.out
assertThat(new String(buffer.toByteArray()), containsString("random content"));
...
// Don't forget to bring back the real System.out at the end of the test
System.setOut(realSysOut);

In the case of checking log output written to System.err, you can equivalently implement it by replacing System.setOut(...) with System.setErr(...) and System.out with System.err in the example above.

Marko Previsic
  • 1,820
  • 16
  • 30