使用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
相关产品推荐
相关产品推荐

