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

使用JUnit5与Mockito测试Java代码时遭遇空指针异常

JUnit5 + Mockito 单元测试空指针异常排查

问题背景

我正在学习JUnit5和Mockito,做Java单元测试时碰到了空指针异常。我开发了一个读取Excel文件的小项目,被测类ExcelProcessor会遍历文件夹里的文件,把每个文件交给ReadingXlsFile类读取内容。但测试中调用readingAllFiles方法时抛出了NullPointerException,搞不懂哪里出问题。

被测代码(SUT)

public class ExcelProcessor {

    private List<File> xlsFiles;

    public ExcelProcessor(List<File> xlsFiles) {
        if(xlsFiles == null){
            throw new NullPointerException("The array is empty");
        }

        this.xlsFiles = xlsFiles;
    }

    public void readingAllFiles()  {

        for(File xlsFile : xlsFiles){
            ReadingXlsFile excelReader = new ReadingXlsFile(xlsFile);
            excelReader.readExcelFile();
        }
    }
}

测试代码

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class ExcelProcessorTest {

    private ExcelProcessor processor;
    private List<File> filesList;

    @Mock
    private ReadingXlsFile excelReaderMock;

    @Mock
    private File mockFile1;

    @Mock
    private File mockFile2;

    @BeforeEach
    public void setup() {
        filesList = new ArrayList<>();
    }

    @Test
    void testReadingAllFiles_whenListExists() {

        filesList.add(mockFile1);
        filesList.add(mockFile2);

        doNothing().when(excelReaderMock).readExcelFile();

        processor = new ExcelProcessor(filesList);

        // Call the method to be tested
        processor.readingAllFiles();

        // Verify that readExcelFile() is called for each file in the list
        verify(excelReaderMock, times(filesList.size())).readExcelFile();

    }
}

堆栈跟踪信息

java.lang.NullPointerException
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:149)
    at com.thomasmelchers.processor.ReadingXlsFile.readExcelFile(ReadingXlsFile.java:26)
    at com.thomasmelchers.processor.ExcelProcessor.readingAllFiles(ExcelProcessor.java:26)
    at com.thomasmelchers.processor.ExcelProcessorTest.testReadingAllFiles_whenListExists(ExcelProcessorTest.java:60)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

问题原因

  • ReadingXlsFile实例未被Mock接管:ExcelProcessor的readingAllFiles方法里直接new ReadingXlsFile(xlsFile)创建实例,这个实例和测试类中的excelReaderMock毫无关联。你对excelReaderMock设置的doNothing()完全不会生效,实际执行的是真实的readExcelFile方法。
  • Mock的File无真实文件支撑:mockFile1和mockFile2是Mockito生成的Mock对象,没有指向真实文件路径。真实的readExcelFile方法尝试用这个Mock File创建FileInputStream时,必然抛出空指针异常。
  • 验证逻辑无效:你试图验证excelReaderMock的方法调用,但实际执行的是新创建的ReadingXlsFile实例,这个验证永远不会通过。

修复方案

核心思路是让ExcelProcessor依赖注入ReadingXlsFile的创建逻辑,而非在方法内直接new,这样才能用Mock替换真实实现。

步骤1:修改ExcelProcessor,支持依赖注入

public class ExcelProcessor {

    private List<File> xlsFiles;
    // 新增依赖:提供ReadingXlsFile的创建逻辑
    private Function<File, ReadingXlsFile> readerFactory;

    // 兼容原有业务代码的构造方法
    public ExcelProcessor(List<File> xlsFiles) {
        this(xlsFiles, ReadingXlsFile::new);
    }

    // 供测试使用的构造方法,传入自定义工厂
    public ExcelProcessor(List<File> xlsFiles, Function<File, ReadingXlsFile> readerFactory) {
        if(xlsFiles == null){
            throw new NullPointerException("The array is empty");
        }
        if(readerFactory == null){
            throw new NullPointerException("Reader factory cannot be null");
        }
        this.xlsFiles = xlsFiles;
        this.readerFactory = readerFactory;
    }

    public void readingAllFiles()  {
        for(File xlsFile : xlsFiles){
            // 通过工厂创建读取器,替代直接new
            ReadingXlsFile excelReader = readerFactory.apply(xlsFile);
            excelReader.readExcelFile();
        }
    }
}

步骤2:修改测试代码,注入Mock读取器

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class ExcelProcessorTest {

    private ExcelProcessor processor;
    private List<File> filesList;

    @Mock
    private ReadingXlsFile excelReaderMock1;

    @Mock
    private ReadingXlsFile excelReaderMock2;

    @Mock
    private File mockFile1;

    @Mock
    private File mockFile2;

    @BeforeEach
    public void setup() {
        filesList = new ArrayList<>();
    }

    @Test
    void testReadingAllFiles_whenListExists() {
        filesList.add(mockFile1);
        filesList.add(mockFile2);

        // 定义工厂逻辑:每个File对应一个Mock读取器
        Function<File, ReadingXlsFile> mockFactory = file -> {
            if (file == mockFile1) {
                return excelReaderMock1;
            } else if (file == mockFile2) {
                return excelReaderMock2;
            }
            throw new IllegalArgumentException("Unknown file");
        };

        // 配置Mock读取器的readExcelFile方法不执行任何操作
        doNothing().when(excelReaderMock1).readExcelFile();
        doNothing().when(excelReaderMock2).readExcelFile();

        // 使用带工厂参数的构造方法创建ExcelProcessor
        processor = new ExcelProcessor(filesList, mockFactory);

        processor.readingAllFiles();

        // 验证每个Mock读取器的方法都被调用一次
        verify(excelReaderMock1, times(1)).readExcelFile();
        verify(excelReaderMock2, times(1)).readExcelFile();
    }
}

替代方案(侵入性较强,不推荐)

如果暂时不想修改ExcelProcessor的构造方法,可以用Mockito的spy或PowerMock拦截new ReadingXlsFile的调用,但这种方式会增加测试的复杂度,仅适合临时场景。

内容的提问来源于stack exchange,提问作者Thomas Melchers

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 11:22:13