Q:

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.

A:

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.

