1

I have this classes:

public class MyClass(){

   private Logger logger;

   @Override
   public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
     this.collector = outputCollector;
     logger = LoggerFactory.getLogger(MyClass.class);
   }

    @Override
    public void execute(Tuple tuple) {
        if (TupleHelpers.isTickTuple(tuple)) {
            logger.info("Received tick tuple, triggering emit of current window counts");
        emitCurrentWindowAvgs();

        } else {

             ...
        }
    }
}

I want test it with Mockito in this way:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

 @Mock
 private Logger logger;

 @InjectMock
 private MyClass myclass;

 @Test
 public void myTest(){
      Tuple tickTuple = MockTupleHelpers.mockTickTuple();
      Myclass myclass = new MyClass();

    // when
    myClass.execute(tickTuple);

    // then
    // verifyZeroInteractions(collector);
    verify(collector).emit(any(Values.class));
}

However I obtain NullPointerException on logger.info("...").

I also tried to add doNothing().when(logger).info(anyString()) but the result does not change.

I searched it but in most cases the problem is the initialization of the mocked object.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
Emanuele Vannacci
  • 321
  • 1
  • 6
  • 15

5 Answers5

1

Looking at your code, I think you have two problems, one being:

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);

I assume this will overwrite your mock even if it is there (assuming, as I do not see when prepare is called).

The other is in your test, where you intantiate a new MyClass-object within the test instead of using the myclass-field which was populated by Mockito. It suffices to use myclass directly without an instantiation. Mockito and its MockitoJunitRunner will take care of its population.

Depending on your logging-framework you may also want to consider a logger-configuration which is dedicated to testing. For example, there exist at least slf4j-test and slf4jtesting for slf4j where you can even assert your logging statements if you must.

If you want to keep your mocked logger and don't want to inject it from outside, you may want to use PowerMockito, or something like that. That way you can return it by mocking also your LoggerFactory.getLogger-call. Supplying a way to easily inject your logger from outside is probably nicer then using PowerMockito for it.

The benefit of using a logger-configuration dedicated to testing is clearly that you do not need to adapt your production code just to test that everything works fine and you can even spare some mocks (and their training) again ;-)

Roland
  • 22,259
  • 4
  • 57
  • 84
1

First of all, you could make it easier for Mockito to inject that logger; like:

public class MyClass(){

  public MyClass() {
    this(LoggerFactory.getLogger(IntermediateStatisticsBolt.class));
  }

  MyClass(Logger logger) { this.logger = logger; }

That also makes it clearer how to pass in a logger for other testing purposes.

Beyond that, in that prepare method:

logger = LoggerFactory.getLogger(IntermediateStatisticsBolt.class);

is a static call.

Mockito can't mock those; you would have to look into PowerMock instead (when starting to test that method).

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • The problem isn't due to `LoggerFactory.getLogger` being a _static_ call... but rather that the static call pretty sure overwrites the already injected mock. Mockito can inject also mocks to private fields even if no accessor is there. Nonetheless, as also others have shown, supplying a way to easily inject the logger is still a good idea. ;-) – Roland Mar 28 '17 at 14:10
1

In your test, you define a mock Logger. However you don't do anything with it.

In your class under test, you create a Logger with LoggerFactory.getLogger(MyClass.class) -- the fact that you've created a mock logger elsewhere has no effect on this. Under all circumstances, including test, this class uses a real logger.

One way to get around this is to make the Logger injectable, for example with the constructor:

 public MyClass(Logger logger) {
      this.logger = logger;
 }

Another is to have a setLogger(Logger logger) method, and yet another is to allow your DI framework to perform field injection (getting into the realms of voodoo).

The simplest (most explicit) way to arrange this injection is to put it in an @Before method:

 @Mock private Logger mockLogger;

 private MyClass myObj; // class under test

 @Before setUp() {
      myObj = new MyClass(mockLogger);
 }

... but you can also use @InjectMock. One reason some people avoid @InjectMock is that it hides the exact issue you are experiencing. You expect it to inject your mock, but if the class has neither a suitable constructor parameter nor a setter, Mockito will silently continue without injecting anything.

There are other strategies for testing logging, which avoid using a mock logger: How to do a JUnit assert on a message in a logger

Community
  • 1
  • 1
slim
  • 40,215
  • 13
  • 94
  • 127
1

the problem is there in the test method, you have defined a field in test class

  @InjectMock
  private MyClass myclass

But you doesn't use that, in the test method you have created a new instance of myClass

Myclass myclass = new MyClass();

myClass field will be hidden by myClass local variable, now the object that you created within the test method will contain the "null" for logger. no one is there to assigned that logger

hunter
  • 3,963
  • 1
  • 16
  • 19
0

IntermediateStatisticsBolt isn't instantiated with your mocked logger, so you 'll get an NPE since it isn't otherwise instantiated in your class. You need to inject this logger (or the real variant in production)

e.g.

new IntermediateStatisticsBolt(logger); // i.e. a mock or real logger

(I would generally tend to instantiate loggers during class initialisation, and not bother testing them, to be honest)

Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • Sometimes logging is a functional requirement and you really want to avoid regression (e.g. you have something monitoring your production logs for specific patterns) -- in which case they should be unit tested. – slim Mar 28 '17 at 13:28
  • That *does* occur occasionally, I grant you. But relatively rarely in my experience – Brian Agnew Mar 28 '17 at 13:29