Embedded MongoDB
It provide a platform neutral way for running mongodb in Java integration tests. Thanks to this java library you can easily run integration test against a real mongo database. It is best to always mock your dependencies in true unit tests, but sometimes you need to test against the real thing.
- It will
- download mongodb (and cache it)
- extract it (and cache it)
- java uses its process api to start and monitor the mongo process
- you run your tests
- java kills the mongo process
Unit testing
How to use it in your unit tests
Add the dependencies to your project
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
Easing the integration
One way to ease the integration is to define your own annotation in MongoDbTest.java
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith({
MongoDbCallback.class
})
public @interface MongoDbTest {
}
And the following MongoDbCallback.java
public class MongoDbCallback implements BeforeAllCallback {
private static MongodExecutable mongo;
@Override public void beforeAll(ExtensionContext context) throws Exception {
if (MONGO != null) {
System.out.println("MongoDB already up and running");
} else {
var version = Version.Main.V4_0;
var port = 27000;
var config = new MongodConfigBuilder()
.version(version)
.net(new Net(port, Network.localhostIsIPv6()))
.build();
mongo = MongodStarter.getDefaultInstance().prepare(config);
mongo.start();
System.out.println("Mongo started {} on port {}", version, port);
}
}
}
You can now annotate your integration test with @MongoDbTest and use the mongoClient connected to localhost:27000
Other ways
- in a Maven build using maven-mongodb-plugin or embedmongo-maven-plugin
- in a Clojure/Leiningen project using lein-embongo
- in a Gradle build using gradle-mongo-plugin
- in a Scala/specs2 specification using specs2-embedmongo
- in Scala tests using scalatest-embedmongo
Log4j 2 asserting log entries with Junit
Lets start with the usage in JUnit
@Rule public LogAccessor logAccessor=new LogAccessor();
@Test @LogAccessorLogLevel(level = "DEBUG", category = "com.cedricwalter")
public void act_arrange_assert() {
// Arrange
// Act
// Assert
logAccessor.assertCount(1).assertLevel(Level.DEBUG).assertMessage("Hellow World");
}
Notes LogAccessorLogLevel is optionnal
Define a new annotation LogAccessorLogLevel .java:
package com.cedricwalter.logging;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
public @interface LogAccessorLogLevel {
String level() default "ERROR";
String category() default "";
}
Create a new appender TestAppender.java
package com.cedricwalter.logging;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class TestAppender extends AbstractAppender {
private final List log = new ArrayList<>();
protected TestAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
super(name, filter, layout);
}
public List getLog() {
return new ArrayList<>(log);
}
@Override
public void append(LogEvent logEvent) {
log.add(logEvent);
}
}
add the Rule class
package com.cedricwalter.logging;
import org.apache.logging.log4j.Level;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import java.util.List;
import static com.innoveo.skye.common.utils.matcher.RegexMatcher.matches;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class LogAccessor extends TestWatcher {
private TestAppender appender;
private LogAccessorLogLevel logAccessorLogLevel;
private List getLog() {
return getAppender().getLog();
}
public LogAccessor assertCount(int expectedLogEntryCount) {
assertThat(getLog().size(), is(expectedLogEntryCount));
return this;
}
public LogAccessor assertLevel(Level expectErrorLevel) {
assertThat(getLog().get(0).getLevel(), is(expectErrorLevel));
return this;
}
public LogAccessor assertMessage(String expectedMessage) {
assertThat(getLog().get(0).getMessage().getFormattedMessage(), matches(expectedMessage));
return this;
}
@Override
protected void starting(Description description) {
appender = new TestAppender("testAppender", null, null);
Level level = getLevel(description); /
/Add appender to root logger
org.apache.logging.log4j.core.Logger rootLogger = (org.apache.logging.log4j.core.Logger) LogManager.getRootLogger();
Configuration configuration = rootLogger.getContext().getConfiguration();
configuration.addLoggerAppender(rootLogger, appender);
String logCategory = getLogCategory(description);
if (logCategory != null) {
changeLoggerLevel(logCategory, level, appender);
}
}
/
* The problem was with the getLoggerConfig() call;
* if the module you are trying to give a new level is not yet
* * registered, this method returns the root logger (or any intermediate sub path registered), and thus instead
* * of altering the level for com.mycompany you will alter root or com level. That's why you have to add a new
* * LoggerConfig in case the module to alter is not yet registered.
* *
* * @param module
* * @param level * @param appender
*/
private static void changeLoggerLevel(final String module, final Level level, TestAppender appender) {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
AbstractConfiguration configuration = (AbstractConfiguration) ctx.getConfiguration();
LoggerConfig loggerConfig = configuration.getLogger(module);
if (loggerConfig != null) {
org.apache.logging.log4j.core.Logger logger = (org.apache.logging.log4j.core.Logger) LogManager.getLogger(module);
configuration.addLoggerAppender(logger, appender);
loggerConfig.setLevel(level);
} else {
loggerConfig = new LoggerConfig(module, level, true);
configuration.addLogger(module, loggerConfig);
ctx.updateLoggers(configuration);
org.apache.logging.log4j.core.Logger logger = (org.apache.logging.log4j.core.Logger) LogManager.getLogger(module);
configuration.addLoggerAppender(logger, appender);
loggerConfig.setLevel(level);
}
ctx.updateLoggers(configuration);
}
@Override
protected void finished(Description description) {
removeAppender(LogManager.ROOT_LOGGER_NAME);
String logCategory = getLogCategory(description);
if (logCategory != null) {
removeAppender(logCategory);
}
}
private void removeAppender(String loggerName) {
org.apache.logging.log4j.core.Logger logger = (org.apache.logging.log4j.core.Logger) LogManager.getLogger(loggerName);
Configuration configuration = logger.getContext().getConfiguration();
LoggerConfig loggerConfig = configuration.getLoggerConfig(loggerName);
loggerConfig.removeAppender(appender.getName());
}
private TestAppender getAppender() {
return appender;
}
private Level getLevel(Description description) {
logAccessorLogLevel = description.getAnnotation(LogAccessorLogLevel.class);
if (logAccessorLogLevel != null) {
return Level.toLevel(logAccessorLogLevel.level());
}
return Level.ERROR;
}
private String getLogCategory(Description description) {
logAccessorLogLevel = description.getAnnotation(LogAccessorLogLevel.class);
if (logAccessorLogLevel != null) {
return logAccessorLogLevel.category().getLoggerName();
}
return null;
}
@Override
public String toString() {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
StringBuilder str = new StringBuilder();
for (LoggerConfig logger : ctx.getConfiguration().getLoggers().values()) {
final String loggerName = LogManager.ROOT_LOGGER_NAME.equals(logger.getName()) ? "Root Logger" : logger.getName();
str.append("Found logger '" + loggerName + "' with level " + logger.getLevel());
}
return str.toString();
}
}