Reputation: 3621
I'm using PowerMock and Mockito to test a Spring controller.
I have the class TestController defined (see below, Snipper #1), and a Unit Test defined for it (see below, Snipper #2). But when I try to unit test it I get an exception (see below, Snipper #3).
If I remove @InjectMocks, remove the TestController instantiation on definition, and do controllerUT = new TestController() in the test function, it works fine (see below, Snipper #4).
This leads me to believe that the static replacement does not happen before @InjectMocks, and my question is if this is the way things work, or am I doing something wrong? Is there a better way to design the code to avoid this issue? I'm guessing people use static log assignment (I did not invent this) so someone must have run into this issue before...
Thanks!
Snippet #1
@Controller
@RequestMapping("/api/test")
public class TestController {
private static final Logger LOG = LoggerFactory.getLogger(TestController.class);
@Autowired
private GeneralService generalService;
@RequestMapping(method=RequestMethod.GET)
public void doSomethingUseful(
HttpServletRequest request,
HttpServletResponse response) {
// nothing userful to do right now
}
}
Snippet #2
@RunWith(PowerMockRunner.class)
@PrepareForTest({TestController.class, LoggerFactory.class})
public class TestControllerTest {
@InjectMocks
private TestController controllerUT = new TestController();
@Mock
private GeneralService service;
@Mock
private Logger loggerMock;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Before
public void setUp() {
PowerMockito.mockStatic(LoggerFactory.class);
when(LoggerFactory.getLogger(any(Class.class))).
thenReturn(loggerMock);
}
@Test
public void doSomethingUsefulTest() {
controllerUT.doSomethingUseful(request, response);
assert(true);
}
}
Snippet #3
Failed to auto configure default logger context
Reported exception:
ch.qos.logback.core.joran.spi.JoranException: Parser configuration error occurred
at ch.qos.logback.core.joran.event.SaxEventRecorder.buildSaxParser(SaxEventRecorder.java:86)
at ch.qos.logback.core.joran.event.SaxEventRecorder.recordEvents(SaxEventRecorder.java:57)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:132)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:96)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:55)
at ch.qos.logback.classic.util.ContextInitializer.configureByResource(ContextInitializer.java:75)
at ch.qos.logback.classic.util.ContextInitializer.autoConfig(ContextInitializer.java:148)
at org.slf4j.impl.StaticLoggerBinder.init(StaticLoggerBinder.java:84)
at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:54)
at org.slf4j.LoggerFactory.bind(LoggerFactory.java:128)
at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:108)
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:279)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:252)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:265)
at com.basicservice.controller.TestController.<clinit>(TestController.java:39)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at javassist.runtime.Desc.getClassObject(Desc.java:43)
at javassist.runtime.Desc.getClassType(Desc.java:152)
at javassist.runtime.Desc.getType(Desc.java:122)
at javassist.runtime.Desc.getType(Desc.java:78)
at com.basicservice.controller.TestControllerTest.<init>(TestControllerTest.java:44)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.createTestInstance(PowerMockJUnit44RunnerDelegateImpl.java:188)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.createTest(PowerMockJUnit44RunnerDelegateImpl.java:173)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:195)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:148)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:102)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:42)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.ClassCastException: org.apache.xerces.jaxp.SAXParserFactoryImpl cannot be cast to javax.xml.parsers.SAXParserFactory
at javax.xml.parsers.SAXParserFactory.newInstance(SAXParserFactory.java:128)
at ch.qos.logback.core.joran.event.SaxEventRecorder.buildSaxParser(SaxEventRecorder.java:79)
... 42 more
Snippet #4
@RunWith(PowerMockRunner.class)
@PrepareForTest({TestController.class, LoggerFactory.class})
public class TestControllerTest {
private TestController controllerUT;
@Mock
private GeneralService service;
@Mock
private Logger loggerMock;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Before
public void setUp() {
PowerMockito.mockStatic(LoggerFactory.class);
when(LoggerFactory.getLogger(any(Class.class))).
thenReturn(loggerMock);
}
@Test
public void doSomethingUsefulTest() {
controllerUT = new TestController();
controllerUT.doSomethingUseful(request, response);
assert(true);
}
}
Upvotes: 4
Views: 5279
Reputation: 42223
One thing to remeber is that @InjectMocks
respect static and final fields i.e. it does not inject mocks in static or final fields. Also note that PowerMock has to spawn a new ClassLoader in order to "instrument" classes, which probably explains the snippet #3.
While I didn't explored your project's ins and outs, I believe you might want to prepare all the Logback/slf4j classes if you might want to use PowerMock. Still keep in mind that Powermock and Mockito are different project and might not work together as one could think.
Upvotes: 1