All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kiwiproject.test.logback.InMemoryAppenderExtension Maven / Gradle / Ivy

There is a newer version: 3.7.0
Show newest version
package org.kiwiproject.test.logback;

import static java.util.Objects.nonNull;
import static org.assertj.core.api.Assertions.assertThat;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import com.google.common.annotations.VisibleForTesting;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.LoggerFactory;


/**
 * A JUnit 5 extension that allows testing messages logged using Logback.
 * Uses {@link InMemoryAppender} to store logged messages, so that tests
 * can retrieve and verify them later.
 * 

* Please see the usage requirements in {@link #InMemoryAppenderExtension(Class)} * and {@link #InMemoryAppenderExtension(Class, String)}, specifically * because this extension requires that a {@link ch.qos.logback.core.Appender} * exists at the time tests are executed. */ public class InMemoryAppenderExtension implements BeforeEachCallback, AfterEachCallback { private final Class loggerClass; private final String appenderName; @Getter private String logbackConfigFilePath; @Getter @Accessors(fluent = true) private InMemoryAppender appender; /** * Create a new instance associated with the given Logback logger class. * The appender name must be the simple name of the logger class * suffixed with "Appender". So, if the logger class is * {@code com.acme.space.modulator.SpaceModulatorServiceTest.class}, then the * appender must be named {@code SpaceModulatorServiceTestAppender}. *

* This appender must exist, e.g., in your {@code logback-test.xml} * configuration file. *

* For example, for a test named {@code MyAwesomeTest}, the * {@code src/test/resources/logback-test.xml} must contain: *

     * <appender name="MyAwesomeTestAppender"
     *     class="org.kiwiproject.test.logback.InMemoryAppender"/>
     *
     * <logger name="com.acme.MyAwesomeTest" level="DEBUG">
     *     <appender-ref ref="MyAwesomeTestAppender"/>
     * </logger>
     * 
*

* Note also the appender must be an {@link InMemoryAppender}. * * @param loggerClass the class of the test logger */ public InMemoryAppenderExtension(Class loggerClass) { this(loggerClass, loggerClass.getSimpleName() + "Appender"); } /** * Create a new instance associated with the given Logback logger class * which has an appender of type {@link InMemoryAppender} with the name * {@code appenderName}. *

* This appender must exist, e.g., in your {@code logback-test.xml} * configuration file. *

* For example, for a test named {@code MyAwesomeTest}, the * {@code src/test/resources/logback-test.xml} must contain: *

     * <appender name="MyAppender"
     *     class="org.kiwiproject.test.logback.InMemoryAppender"/>
     *
     * <logger name="com.acme.AnAwesomeTest" level="DEBUG">
     *     <appender-ref ref="MyAppender"/>
     * </logger>
     * 
*

* You then use {@code "MyAppender"} as the value for the * {@code appenderName} argument. *

* Note also the appender must be an {@link InMemoryAppender}. * * @param loggerClass the class of the test logger * @param appenderName the name of the {@link InMemoryAppender} */ public InMemoryAppenderExtension(Class loggerClass, String appenderName) { this.loggerClass = loggerClass; this.appenderName = appenderName; } /** * The custom Logback configuration to use if the logging system needs to be reset. *

* For example: * *

     * {@literal @}RegisterExtension
     *  private final InMemoryAppenderExtension inMemoryAppenderExtension =
     *          new InMemoryAppenderExtension(InMemoryAppenderTest.class)
     *                  .withLogbackConfigFilePath("acme-logback-test.xml");
     * 
* * If this is not set, then the default Logback configuration files are used * in the order {@code logback-test.xml} followed by {@code logback.xml}. * * @param logbackConfigFilePath the location of the custom Logback configuration file * @return this extension, so this can be chained after the constructor * @see Tests failing because Logback appenders don't exist (#457) */ public InMemoryAppenderExtension withLogbackConfigFilePath(String logbackConfigFilePath) { this.logbackConfigFilePath = logbackConfigFilePath; return this; } /** * Exposes the {@link InMemoryAppender} associated with {@code loggerClass}. * It can be obtained via the {@link #appender()} method. Usually, tests * will store an instance in their own {@link org.junit.jupiter.api.BeforeEach BeforeEach} * method. For example: *
     * class SpaceModulatorTest {
     *
     *     {@literal @}RegisterExtension
     *      private final InMemoryAppenderExtension inMemoryAppenderExtension =
     *                 new InMemoryAppenderExtension(SpaceModulatorServiceTest.class);
     *
     *      private InMemoryAppender appender;
     *
     *     {@literal @}BeforeEach
     *      void setUp() {
     *          this.appender = inMemoryAppenderExtension.appender();
     *
     *          // additional set up...
     *      }
     *
     *      // tests...
     *  }
     * 
* * @param context the current extension context; never {@code null} */ @Override public void beforeEach(ExtensionContext context) { var logbackLogger = (Logger) LoggerFactory.getLogger(loggerClass); var rawAppender = getAppender(logbackLogger); assertThat(rawAppender) .describedAs("Expected an appender named '%s' for logger '%s' of type %s", appenderName, loggerClass.getName(), InMemoryAppender.class.getName()) .isInstanceOf(InMemoryAppender.class); appender = (InMemoryAppender) rawAppender; } @Nullable @VisibleForTesting @SuppressWarnings("java:S106") Appender getAppender(Logger logbackLogger) { var rawAppender = logbackLogger.getAppender(appenderName); if (nonNull(rawAppender)) { return rawAppender; } // Write to stdout since the logging system might be hosed... System.out.printf( "Appender %s not found on logger %s; attempt fix by resetting logging configuration using config: %s%n", appenderName, loggerClass, logbackConfigFilePath); System.out.println("You can customize the logging configuration using #withLogbackConfigFilePath"); // Reset the Logback logging system getLogbackTestHelper().resetLogbackWithDefaultOrConfig(logbackConfigFilePath); // Try again and return whatever we get. It should not be null after resetting, unless // the reset failed, or the appender was not configured correctly. return logbackLogger.getAppender(appenderName); } @VisibleForTesting protected LogbackTestHelper getLogbackTestHelper() { return new LogbackTestHelper(); } /** * Clears all logging events from the {@link InMemoryAppender} to ensure each * test starts with an empty appender. * * @param context the current extension context; never {@code null} * @see InMemoryAppender#clearEvents() */ @Override public void afterEach(ExtensionContext context) { appender.clearEvents(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy