Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking and verifying SLF4J with JMockit

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.

like image 767
mergoth Avatar asked Nov 17 '16 16:11

mergoth


1 Answers

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.

like image 130
Thunderforge Avatar answered Sep 27 '22 21:09

Thunderforge