I'm trying to use JMockit to test that a certain logging operation takes place:
public class LogClass1 {
public void doLog() {
Logger logger = LogManager.getLogger(LogClass1.class);
logger.info("This is a log message for {}", "arg1");
}
}
public class LogClass1Test {
#Mocked
private Logger logger;
#Tested
private LogClass1 x;
#Before
public void setup() {
x = new LogClass1();
}
#Test
public void testDoLog() {
new Expectations() {
{
logger.info("This is a log message for {}", "arg1");
}
};
x.doLog();
}
}
But this results in a "missing 1 invocation to org.apache.logging.log4j.Logger#info" error.
I've done similar mocking with log4j 1.x in the past, and I haven't had this problem. I'm wondering if there's some issue because log4j 2.x seems to have many more overloads of its info() methods.
I tried changing "arg1" to (Object)"arg1" in the unit test to see if I could get it to match the signature. This didn't help.
Any thoughts on how I can get this to work?
Note that Logger is an interface, and that LogClass1 obtains an instance of it through the LogManager.getLogger static factory method. So, obviously, it creates an instance of some Logger implementation class. And said class is not being mocked in the test.
What the test needs to do is to mock LogManager, so it returns the #Mocked Logger instance. That is, add a #Mocked LogManager field to the test class.
(Also, no need for that setup method since #Tested creates an instance automatically.)
Related
In a unit test in a spring-boot environment with slf4j and logback, I want to make sure LOGGER.isTraceEnabled() returns true for a particular test class only.
The reason for that is, that sometimes we have slow and non trivial code guarded with if (LOGGER.isTraceEnabled()) {...}. In a unit test we want to make sure it does not break the application if we switch on trace.
Code under test:
public class ClassUnderTest{
private static final Logger LOGGER = LoggerFactory.getLogger(ClassUnderTest.class);
public void doSomething(Calendar gegenwart) {
if (LOGGER.isTraceEnabled()) {
// non trivial code
}
}
}
Test code:
public class ClassUnderTestUnitTest{
#Test
public void findeSnapshotMitLueke() {
ClassUnderTest toTestInstance = new ClassUnderTest ();
// I want to make sure trace logging is enabled, when
// this method is executed.
// Trace logging should not be enabled outside this
// test class.
toTestInstance.doSomething(Calendar.getInstance());
}
}
You can easily create a static mock of LoggerFactory using PowerMock and cause it to return a regular mock of Logger (using EasyMock). Then you simply define mock implementation of Logger.isTraceEnabled() and Logger.trace().
Off the top of my head:
#PrepareForTest({ LoggerFactory.class })
#RunWith(PowerMockRunner.class) // assuming JUnit...
public class ClassUnderTestUnitTest{
#Test
public void findeSnapshotMitLueke() {
Logger mockLogger = EasyMock.createMock(Logger.class);
EasyMock.expect(mockLogger.isTraceEnabled()).andReturn(true);
EasyMock.expect(mockLogger.trace(any()));
EasyMock.expectLastCall().anyTimes() // as trace is a void method.
// Repeat for other log methods ...
PowerMock.mockStatic(LoggerFactory.class);
EasyMock.expect(LoggerFactory.getLogger(ClassUnderTest.class)
.andReturn(mockLogger);
PowerMock.replay(mockLogger, LoggerFactory.class);
ClassUnderTest toTestInstance = new ClassUnderTest ();
// I want to make sure trace logging is enabled, when
// this method is executed.
// Trace logging should not be enabled outside this
// test class.
toTestInstance.doSomething(Calendar.getInstance());
// After the operation if needed you can verify that the mocked methods were called.
PowerMock.verify(mockLogger).times(...);
PowerMock.verifyStatic(LoggerFactory.class).times(...);
}
}
In case you don't want to use a framework like powermock you can do the following trick:
public class ClassUnderTest {
private Supplier<Logger> loggerSupplier = () -> getLogger(ClassUnderTest.class);
public void doSomething(Calendar gegenwart) {
if (loggerSupplier.get().isTraceEnabled()) {
// non trivial code
}
}
}
Now you are able to mock the logger:
public class ClassUnderTestUnitTest{
#Mock
private Supplier<Mock> loggerSupplier;
#Mock
private Logger logger;
#Test
public void findeSnapshotMitLueke() {
ClassUnderTest toTestInstance = new ClassUnderTest ();
when(loggerSupplier.get()).thenReturn(logger);
when(logger.isTraceEnabled()).thenReturn(true)
toTestInstance.doSomething(Calendar.getInstance());
// verifyLogger();
}
}
I developed a kind of wrapper to make it work as a custom logger. I'm instantiating this class using #CustomLog Lombok annotation just to make it easier and cleaner. The tricky thing comes next: the idea behind this wrapper is to use a common logger (as org.slf4j.Logger) along with a custom monitor class that each time I call log.error(), the proper message gets logged in the terminal and the event is sent to my monitoring tool (Prometheus in this case).
To achieve this I did the following classes:
CustomLoggerFactory the factory called by Lombok to instantiate my custom logger.
public final class CustomLoggerFactory {
public static CustomLogger getLogger(String className) {
return new CustomLogger(className);
}
}
CustomLogger will receive the class name just to then call org.slf4j.LoggerFactory.
public class CustomLogger {
private org.slf4j.Logger logger;
private PrometheusMonitor prometheusMonitor;
private String className;
public CustomLogger(String className) {
this.logger = org.slf4j.LoggerFactory.getLogger(className);
this.className = className;
this.monitor = SpringContext.getBean(PrometheusMonitor.class);
}
}
PrometheusMonitor class is the one in charge of creating the metrics and that kind of things. The most important thing here is that it's being managed by Spring Boot.
#Component
public class PrometheusMonitor {
private MeterRegistry meterRegistry;
public PrometheusMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
}
As you may noticed, to access PrometheusMonitor from CustomLogger I need an additional class in order to get the Bean / access the context from a non Spring managed class. This is the SpringContext class which has an static method to get the bean by the class supplied.
#Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
public static <T extends Object> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
SpringContext.context = context;
}
}
So all this works just fine when running the application. I ensure to load SpringContext class before anything else, so once each CustomLogger gets instantiated it just works.
But the BIG issue comes here: this is not working while unit testing my app. I tried many things and I saw some solutions that may help me but that I'm trying to avoid (e.g. using PowerMockito). Lombok is processing #CustomLog annotation before any #Before method I add to my test class. Once getBean() method is called I get an exception cause context is null.
My guesses are that I could solve it if I can force the SpringContext to be loaded before Lombok does its magic, but I'm not sure that's even possible. Many thanks for taking your time to read this. Any more info I can provide just let me know.
NOTE: It sounds like your custom logging needs are better served by logging to slf4j as normal, and registering an additional handler with the slf4j framework so that slf4j will forward any logs to you (in addition to the other handlers, such as the one making the log files).
Lombok is processing #CustomLog
The generated log field is static. If an annotation is going to help at all, you'd need #BeforeClass, but that probably also isn't in time. Lombok's magic doesn't seem relevant here. Check out what delombok tells you lombok is doing: It's just.. a static field, being initialized on declaration.
Well I managed to solve this issue changing a little how the CustomLogger works. Meaning that instead of instantiating monitor field along with the logger, you can do it the first time you'll use it. E.g.:
public class CustomLogger {
private org.slf4j.Logger logger;
private Monitor monitor;
public CustomLogger(String className) {
this.logger = org.slf4j.LoggerFactory.getLogger(className);
}
public void info(String message) {
this.logger.info(message);
}
public void error(String message) {
this.logger.error(message);
if (this.monitor == null) {
this.monitor = SpringContext.getBean(PrometheusMonitor.class);
}
this.monitor.send(message);
}
}
But after all I decided to not follow this approach because I don't think it's the best one possible and worth it.
I want to test a method whether it's creating correct logs or not. My class looks like this:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
class EventLogHandler {
private final Logger logger = LoggerFactory.getLogger(EventLogHandler.class);
private final Marker eventMarker = MarkerFactory.getMarker("EVENT");
public void handle(final Event event) {
final String log = SomeOtherClass.createLog(event);
logger.info(eventMarker, log);
}
}
I've seen some examples/solutions for testing logs but all of them are using Log4j, which we are not using in the project. I can only use Log4j2 of spring-boot, Slf4j and Logback classic.
How can I test that handle(...) method with my existing dependencies?
With the current implementation you cannot test/verify the invocations on logger without engaging a logging system and asserting on its output e.g. introduce logback and configure it with a stdout appender and capture stdout and assert against it etc.
In order to test your class without doing any of that you have to get your hands on the logger instance in use in EventLogHandler. The current implementation makes this difficult by constructing the logger like this:
private final Logger logger = LoggerFactory.getLogger(EventLogHandler.class);
A common approach to testing in this scenario is to refactor the creation of logger in such a way that you can inject a mocked instance into EventLogHandler when running your tests. For example:
class EventLogHandler {
private final Marker eventMarker = MarkerFactory.getMarker("EVENT");
private final Logger logger;
public EventLogHandler() {
this(LoggerFactory.getLogger(EventLogHandler.class));
}
// probably only used by your test case
public EventLogHandler(Logger logger) {
this.logger = logger;
}
public void handle(final Event event) {
logger.info(eventMarker, log);
}
}
Then test it like so:
#Test
public void someTest() {
Logger logger = Mockito.mock(Logger.class);
EventLogHandler sut = new EventLogHandler(logger);
sut.handle(event);
// verify that the right state is extracted from the given event and that the correct marker is used
Mockito.verify(logger).info(..., ...);
}
A less common alternative would be to use Powermock to allow you to mock this call: LoggerFactory.getLogger(EventLogHandler.class); and then use Mockito to verify calls onto it in the same way as is shown above..
I'm using com.portingle:slf4jtesting:1.1.3 to aid testing of some logging functionality.
My problem is that the devs at com.portingle are strong advocates of dependency injection and suggest only dependency injection for utilising their slf4jtesting::ILoggerFactory utility (an implementation of slf4j which stores up the log entries for easy testing and validation).
With dependency injection, I could just create my slf4j loggers in my classes like this and inject either a production or test LoggerFactory:
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
public class Example1 {
private final Logger logger;
public Example1(ILoggerFactory lf) {
this.logger = lf.getLogger(Example1.class.getName());
}
public void aMethodThatLogs() {
logger.info("Hello World!");
}
}
Reasonable enough but I've got a legacy app and all my loggers are already coded and used sometimes in static code blocks / methods, so standard DI constructor injection won't work.
Currently I am doing this:
private static final Logger log = LoggingUtils.getLogger(
RequestLoggingFilter.class);
and LoggingUtils looks like this:
public class LoggingUtils {
private LoggingUtils() {
}
private static ILoggerFactory iLoggerFactory =
LoggerFactory.getILoggerFactory();
/**
* We don't want to call this in production.
*/
public static void switchToTestLogging() {
iLoggerFactory = Settings.instance().enableAll().buildLogging();
}
/**
* Return logger for a class, of whatever implementation is running,
* e.g. test or prod logger.
*
* #param loggingClass the class doing the logging
* #return logger
*/
public static Logger getLogger(Class loggingClass) {
return iLoggerFactory.getLogger(loggingClass.getName());
}
So in tests, I can switch to slf4jtesting::ILoggerFactory with a call to my switchToTestLogging(), but the end result is that I have slf4jtesting code in my production code.
Alternatively, I could make the iLoggerFactory public so that the tests can just replace it when necessary, but it would be bad practice to allow any production code to do that.
Lastly, I could use reflection to hack the private ILoggerFactory instance in my LoggingUtils class and assign a test LoggerFactory during testing:
#BeforeAll
public static void setupLogging()
throws NoSuchFieldException, IllegalAccessException {
Field loggerFactoryField =
LoggingUtils.class.getDeclaredField("iLoggerFactory");
loggerFactoryField.setAccessible(true);
loggerFactoryField.set(null,
Settings.instance().enableAll().buildLogging());
}
but that's also not exactly 'best practice'.
Is there any way to keep the ILoggerFactory instance private, avoid reflection and keep the test libraries out of production?
I am not a big fan of static coupling, but technically, you are focussing too much on implementation concerns.
You can remove switchToTestLogging altogether
public class LoggingUtils {
private LoggingUtils() {
}
private static ILoggerFactory iLoggerFactory;
/**
* Return logger for a class
*
* #param loggingClass the class doing the logging
* #return logger
*/
public static Logger getLogger(Class loggingClass) {
//Lazy loading.
if(iLoggerFactory == null) {
iLoggerFactory = LoggerFactory.getILoggerFactory();
}
return iLoggerFactory.getLogger(loggingClass.getName());
}
}
and in testing mock the factory method to return the desired logger when invoked.
PowerMockito should be able to let you mock static members.
#RunWith(PowerMockRunner.class)
#PrepareForTest(LoggingUtils.class) //<-- important
public class SomeTest {
#Test
public void someTestMethod() {
//Arrange
//get the logger used in testing
ILoggerFactory testLoggerFactory = Settings.instance().enableAll().buildLogging();
//set up util for mocking
PowerMockito.mockStatic(LoggingUtils.class);
//setup mocked member
Mockito.when(LoggingUtils.getLogger(any(Class.class)))
.thenAnswer(i -> testLoggerFactory.getLogger(i.getArguments()[0].getName()));
//Act
//call subject under test that is coupled to LoggingUtils
//Assert
//...
}
}
The LoggingUtils is now only concerned with production concerns, and PowerMockito allows you to stub test loggers whenever LoggingUtils.getLogger is invoked while exercising tests.
Disclaimer: This has not been tested. Provided based on my recollection of the framework.
With that done I would strongly advise refactoring your code to follow my SOLID practices that would make your code cleaner and more maintainable. Hacks like these are code smells and a clear indicator of poor design. Just because there are tools that allow a work around does not take away from the poor design choices made.
I have a class with SLF4J logger instanced like:
public class MyClass {
private static final Logger log = LoggerFactory.getLogger(MyClass.class);
public void foo() {
log.warn("My warn");
}
}
And I need to test it with JMockit like:
#Test
public void shouldLogWarn(#Mocked Logger log) throws Exception {
new Expectations() {{
log.warn(anyString);
}};
MyClass my = new MyClass();
my.foo();
}
After searching a lot I figured out, I need to use MockUp somehow. But can't get it how exactly.
Btw, I'm using last version of JMockit(1.29) where you no more can setField(log) for final static fields.
JMockit has the #Capturing annotation that works for this situation
Indicates a mock field or a mock parameter for which all classes extending/implementing the mocked type will also get mocked.
Future instances of a capturing mocked type (ie, instances created sometime later during the test) will become associated with the mock field/parameter. When recording or verifying expectations on the mock field/parameter, these associated instances are regarded as equivalent to the original mocked instance created for the mock field/parameter.
This means that if you annotate it with #Capturing instead of #Mocked, every Logger that is created during the test run will be associated with one you annotated. So the following works:
#Test
public void shouldLogWarn(#Capturing final Logger logger) throws Exception {
// This really ought to be a Verifications block instead
new Expectations() {{
logger.warn(anyString);
}};
MyClass my = new MyClass();
my.foo();
}
As a side note, if all you want to do is verify that a method is called, it's better to use Verifications instead, since that is what it is intended for. So your code would look like this:
#Test
public void shouldLogWarn(#Capturing final Logger logger) throws Exception {
MyClass my = new MyClass();
my.foo();
new Verifications() {{
logger.warn(anyString);
}};
}
Alternatively, you can use #Mocked on both Logger and LoggerFactory
In some cases, #Capturing won't work as intended due to intricacies of how the annotation works. Fortunately, you can also get the same effect by using #Mocked on both Logger and LoggerFactory like so:
#Test
public void shouldLogWarn(#Mocked final LoggerFactory loggerFactory, #Mocked final Logger logger) throws Exception {
MyClass my = new MyClass();
my.foo();
new Verifications() {{
logger.warn(anyString);
}};
}
Note: JMockit 1.34 through 1.38 has a bug that prevents this from working with slf4j-log4j12, and possibly other dependencies of SLF4J. Upgrade to 1.39 or later if you run into this bug.