Reputation: 393
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!
Upvotes: 23
Views: 27280
Reputation: 81
If you are using Springboot, you can use CapturedOutput and check if what you logged appeared.
Your class:
@Slf4j
public class MyClass {
public void myLogFunction() {
log.info("Hello world !";
}
}
Your test class:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(OutputCaptureExtension.class)
class MyTestClass {
@Test
void MyLogTest (CapturedOutput output) {
// my conditions
when(...).thenReturn(...);
// my tests having some logs
myLogFunction();
// checking my logs appeared in log output
Assertions.assertTrue(output.getOut().contains("Hello world !"));
}
}
Upvotes: 1
Reputation: 4126
If what you want to do is to check if the class under test writes correct logs, another way is to configure a ListAppender
(which is already provided by LogBack) and point your logger name to that appender in your logging configuration. The appender writes the logs to a java.util.List
and then you can get the appender via the logging framework's API. Then you can check the contents of the list.
LogBack's default ListAppender is not thread-safe so you may want to write something similar with any synchronization if necessary.
Here is how we do it with SLF4j and logback:
import ch.qos.logback.classic.Logger
import ch.qos.logback.core.read.ListAppender
....
Logger logger = (Logger) LoggerFactory.getLogger("some.logger")
ListAppender appender = (ListAppender) logger.getAppender("SOME_LIST")
In your logback.xml file, you configure the appender:
<appender name="SOME_LIST" class="ch.qos.logback.core.read.ListAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<logger name="some.logger" level="DEBUG">
<appender-ref ref="SOME_LIST" />
</logger>
Upvotes: 1
Reputation: 1893
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"));
}
}
Upvotes: 23
Reputation: 1960
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.
Upvotes: 3
Reputation: 2550
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)
Upvotes: 7
Reputation: 31878
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.
Upvotes: 3