Использование PowerMock для издевательских статических свойств с помощью @InjectMocks

Я использую PowerMock и Mockito для тестирования контроллера Spring.

У меня определен класс TestController (см. ниже, Snipper # 1) и Unit Test, определенные для него (см. ниже, Snipper # 2). Но когда я пытаюсь Unit Test, я получаю исключение (см. Ниже, Snipper # 3).

Если я удаляю @InjectMocks, удаляю экземпляр TestController при определении и выполняю controllerUT = new TestController() в тестовой функции, он отлично работает (см. ниже, Snipper # 4).

Это заставляет меня думать, что статическая замена не происходит до @InjectMocks, и мой вопрос в том, как это работает, или я делаю что-то неправильно? Есть ли лучший способ разработать код, чтобы избежать этой проблемы? Я предполагаю, что люди используют статическое назначение журналов (я этого не изобретал), поэтому кто-то должен был столкнуться с этой проблемой раньше...

Спасибо!

Фрагмент # 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
 }
}

Фрагмент # 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);
 }
}

Фрагмент # 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
</init></clinit></clinit>

Фрагмент # 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);
 }
}
1 ответ

Следует помнить, что @InjectMocks соблюдает статические и конечные поля, то есть не вводит mocks в статические или конечные поля. Также обратите внимание, что PowerMock должен порождать новый ClassLoader для "инструментальных" классов, что, вероятно, объясняет фрагмент # 3.

Пока я не изучал ваши проекты, я считаю, что вам может понадобиться подготовить все классы Logback/slf4j, если вы захотите использовать PowerMock. Имейте в виду, что Powermock и Mockito - это разные проекты и могут не работать вместе, как можно было бы подумать.

licensed under cc by-sa 3.0 with attribution.