Search code examples
javamavenlog4jcdi

@Inject field during test phase


I'm adding logging functionality to my application and I'm using slf4j implemented by log4j. To simplify my code I have a helper class to produce a logger (the idea is not mine, I took it from Beginning Java EE 7 by Antonio Goncalves), the class is like this:

import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggerProducer {

    @Produces
    public Logger createLogger(InjectionPoint injPoint) {
        return LoggerFactory.getLogger(injPoint.getMember().getDeclaringClass());
    }
}

So in my classes all I have to do (in theory) is:

public class SomeClass {
    @Inject private Logger logger; // this could be private or default, doesn't matter
}

The problem is that during test phase I don't have CDI enabled because I'm running the code outside the container. I can't inject manually because I have some abstract classes with their own logger, so I can't instantiate that class and assign it a Logger instance.

Is there any way to "enable" CDI during test phase? My project is built with Maven and I'll be deploying to a Wildfly 10 Server. Thanks in advance for your answers.

Note

I cannot do something like

public class SomeClassTest {
    private SomeClass someClass; // this is the class I want to test

    @Before
    public void init() {
        someClass.logger = LoggerFactory.getLogger(SomeClass.class);
    }

    @Test
    public void someTest(){...}
}

because I have some abstract classes with their own private Logger logger property and I need to keep it that way (I can't declare it protected ) because I want to keep a trace of where a message is thrown, so I need to keep the logger private. E.g. I have something like

public abstract class MyAbstract {
    @Inject
    private Logger logger;
}

public class MyConcrete extends MyAbstract {
    @Inject
    private Logger logger;
}

I could, from the test class, set MyConcrete.logger as default but I cant do that forMyAbstract.loggerbecause they are in different packages and I can't instantiateMyAbstract`, am I explaining correctly?

Note 2

My project structure is something like this:

package common

import org.slf4j.logger
...

public abstract class Generic {
    @Inject
    private Logger logger;

    public void doSomethingGeneric(){
        // do something and log it
    }
}

package specific

import ...

public class Concrete extends Generic{
    @Inject
    Logger logger;

    // some concrete methods...
}

package specific

public class ConcreteTest {
    private Concrete concrete;

    @Before
    public void init() {
        concrete = new Concrete();
        concrete.logger = LoggerFactory.getLogger(Concrete.class); // Dependency injection by hand
    }

    // some @Test methods
}

Eventually, a @Test method from ConcreteTest calls Concrete.doSomethingGeneric() as it's inherited to the concrete instance. The problem is that doSomethingGeneric() logs with the Generic logger and I don't know how to satisfy that dependency in a clean way (I prefer not to hack my code with setter methods unnecessary for production)


Solution

  • You can do this, using mockito test units:

    @RunWith(MockitoJUnitRunner.class)
    public class MyBeanTest {
    
      @Spy
      private Logger logger = LoggerProducer.getLogger();
    
      @InjectMocks
      private MyBean myTestBean; // = new MyBean(parameters). explicit if no default constructir
    
      @Test
      public void verifySomeLogic() {
        myTestBean.doSomething();
      }
    }
    

    Here, the mockito runtime and proxy will manage the injection, and you can even do verification on the logger itself.