SpringBoot中ClassB测试遇ClassA静态Logger空指针的解决方法
问题场景
有如下ClassA类(带有静态Logger成员narrLogger,标注@Profile("!ut")等注解):
@Log4j2 @Profile("!ut") @Component("CALC") @ConditionalOnProperty(name = "CALC", havingValue = "VAL") public class ClassA extends ClassMain { public static org.apache.logging.log4j.Logger narrLogger; @Autowired IHCRefDataMgr ihcRefDataMgr; public ClassA(IHCRefDataMgr ihcRefDataMgr) { super(ihcRefDataMgr); this.ihcRefDataMgr = ihcRefDataMgr; } public boolean calculate(Object msg) { narrLogger = Narratives.getNarrativeLogger(msg); return true; } }
另有ClassB类直接调用该静态Logger:
@Data @Component @ConditionalOnProperty(name = "CALC", havingValue = "VAL") @Log4j2 public class ClassB { Boolean prepareMap() { Boolean status = true; try { Set<String> securityKeys = ConcurrentHashMap.newKeySet(); ClassA.narrLogger.trace("Security Keys for given BA: {}", securityKeys); // 业务处理逻辑 } catch (Exception e) { status = false; System.out.println(e); } return status; } }
编写的ClassB SpringBoot测试用例如下:
@Log4j2 @SpringBootTest @TestPropertySource(properties = {"spring.profiles.active=test,ut", "calculator.name = ClassA", "eureka.client.enabled = false"}) public class ClassBTest { @Autowired ClassB classb; @Test public void prepareMapTest() { var status = classb.prepareMap(); Assertions.assertEquals(true, status); } }
运行测试时,因ClassA.narrLogger为空抛出空指针异常,且无需测试narrLogger本身,如何Mock ClassA以通过测试?
解决方案
方法1:直接初始化静态Logger(最简单)
在测试执行前,直接给静态成员narrLogger赋值一个合法的Logger实例,无需额外依赖:
@Log4j2 @SpringBootTest @TestPropertySource(properties = {"spring.profiles.active=test,ut", "calculator.name = ClassA", "eureka.client.enabled = false"}) public class ClassBTest { @Autowired ClassB classb; @BeforeEach void setUp() { // 用ClassA的Logger实例初始化静态成员 ClassA.narrLogger = LogManager.getLogger(ClassA.class); // 也可以用NullLogger避免输出日志 // ClassA.narrLogger = NullLogger.getInstance(); } @Test public void prepareMapTest() { var status = classb.prepareMap(); Assertions.assertEquals(true, status); } }
方法2:用Mockito静态Mock(需Mockito 3.4+)
如果需要更规范地Mock静态成员,可使用Mockito的静态Mock功能,需先引入mockito-inline扩展依赖:
<!-- Maven依赖示例 --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>
测试类修改如下:
@Log4j2 @SpringBootTest @TestPropertySource(properties = {"spring.profiles.active=test,ut", "calculator.name = ClassA", "eureka.client.enabled = false"}) public class ClassBTest { @Autowired ClassB classb; @Test public void prepareMapTest() { // Mock ClassA的静态成员 try (MockedStatic<ClassA> mockedClassA = Mockito.mockStatic(ClassA.class)) { Logger mockLogger = Mockito.mock(Logger.class); // 指定访问ClassA.narrLogger时返回Mock实例 mockedClassA.when(() -> ClassA.narrLogger).thenReturn(mockLogger); var status = classb.prepareMap(); Assertions.assertEquals(true, status); // 可选:验证Logger的调用行为 mockedClassA.verify(() -> ClassA.narrLogger.trace(Mockito.anyString(), Mockito.anySet())); } } }
方法3:用PowerMock(兼容旧版本Mockito)
若项目使用旧版本Mockito,可通过PowerMock实现静态成员Mock,先添加PowerMock依赖:
<!-- Maven依赖示例 --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency>
测试类修改如下:
@Log4j2 @SpringBootTest @TestPropertySource(properties = {"spring.profiles.active=test,ut", "calculator.name = ClassA", "eureka.client.enabled = false"}) @RunWith(PowerMockRunner.class) @PrepareForTest(ClassA.class) public class ClassBTest { @Autowired ClassB classb; @Before void setUp() { Logger mockLogger = Mockito.mock(Logger.class); PowerMockito.mockStatic(ClassA.class); // 直接设置静态成员值 Whitebox.setInternalState(ClassA.class, "narrLogger", mockLogger); } @Test public void prepareMapTest() { var status = classb.prepareMap(); Assertions.assertEquals(true, status); } }
额外重构建议
直接调用静态成员会导致代码耦合度高、测试困难。若后续有重构计划,建议:
- 将
ClassA.narrLogger改为非静态成员,通过依赖注入传递给ClassB - 让
ClassB注入ClassA实例,通过实例访问Logger(需调整ClassA的@Profile注解或测试环境配置,确保测试时能实例化ClassA)
内容的提问来源于stack exchange,提问作者Aditya Sethi
相关产品推荐
相关产品推荐

