You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 21:25:16