Junit tips and tricks

Thursday, Jan 28, 2021 | 4 minute read | Updated at Thursday, Mar 6, 2025

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

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();
    }
}

© 1997 - 2025 Cédric Walter blog

🌱 Powered by Hugo with theme Dream.

Others

If you like my work or find it helpful, please consider buying me a cup of coffee ☕️. It inspires me to create and maintain more projects in the future. 🦾

It is better to attach some information or leave a message so that I can record the donation 📝 , thank you very much 🙏.

Copyright information

All editorial content and graphics on our sites are protected by U.S. copyright, international treaties, and other applicable copyright laws and may not be copied without the express permission of Cedric Walter, which reserves all rights. Reuse of any of Cedric Walter editorial content and graphics for any purpose without The author ’s permission is strictly prohibited.

DO NOT copy or adapt the HTML or other code that this site creates to generate pages. It also is covered by copyright.

Reproduction without explicit permission is prohibited. All Rights Reserved. All photos remain copyright © their rightful owners. No copyright infringement is intended.

Disclaimer: The editor(s) reserve the right to edit any comments that are found to be abusive, offensive, contain profanity, serves as spam, is largely self-promotional, or displaying attempts to harbour irrelevant text links for any purpose.

About me

Cédric Walter is a French-Swiss software engineer based in Zurich, Switzerland. PGP: DF52 ADDA C81A 08A6