测试时Stub Apache POI的XWPFDocument.write方法避免生成实体文档
我有一个用Apache POI API写入Word文档的方法,现在想测试这个方法,但不想生成实际的外部文档。负责生成文档的是XWPFDocument.write(OutputStream)方法,我希望让这个方法执行doNothing(),但Mock XWPFDocument时,既出现过“mock对象无调用记录”的问题,还碰到了下面的错误:
org.mockito.exceptions.base.MockitoException:
Only void methods can doNothing()!
Example of correct use of doNothing():
doNothing().
doThrow(new RuntimeException())
.when(mock).someVoidMethod();
Above means:
someVoidMethod() does nothing the 1st time but throws an exception the 2nd time is called
待测试的方法
public String populateDoc(String text) { try (XWPFDocument doc = new XWPFDocument(); OutputStream out = Files.newOutputStream(OUT)) { StringBuilder sb = new StringBuilder(); LOGGER.info("Document Created."); String[] lines = text.split("\n"); for (String line : lines) { XWPFParagraph para = doc.createParagraph(); XWPFRun run = para.createRun(); run.setText(line); sb.append(line); sb.append("\n"); } LOGGER.info("Document saved."); doc.write(out); return sb.toString(); } catch (IOException ioe) { LOGGER.error(ioe); } return null; }
当前生成实体文档的测试代码(不符合需求)
@ExtendWith(MockitoExtension.class) public class FileWriterTest { @Test void testFileContainsAParagraph() { FileWriter fileWriter = new FileWriter("src/test/resources/outputTest.docx"); String text = "Hello World.\n"; Assertions.assertEquals(text, fileWriter.populateDoc(text)); } }
我尝试过的测试代码
@Test void testTextReturnsHello() throws IOException { XWPFDocument doc = mock (XWPFDocument.class); FileWriter fileWriter = new FileWriter(); OutputStream out = Files.newOutputStream(Paths.get("src/test/resources/outputTest.docx")); String text = "Hello"; Mockito.doNothing().when(doc).write(out); Assertions.assertEquals(text, fileWriter.populateDoc(text)); Mockito.verify(doc).write(out); }
解决方案
核心问题是待测试方法里直接new了XWPFDocument和OutputStream,导致Mock的对象根本没被用到,所以才会出现“无调用记录”的问题;另外write本身是void方法,之前的错误大概率是因为对象未正确注入而非语法问题。
步骤1:重构待测试类,通过依赖注入解耦
要Mock这些对象,必须让类通过构造方法接收依赖,而非在方法内部直接实例化。修改FileWriter类:
public class FileWriter { private final Supplier<XWPFDocument> docSupplier; private final Supplier<OutputStream> outputStreamSupplier; // 生产环境用构造方法 public FileWriter(Path outputPath) { this(() -> new XWPFDocument(), () -> Files.newOutputStream(outputPath)); } // 测试用构造方法,注入Mock依赖 public FileWriter(Supplier<XWPFDocument> docSupplier, Supplier<OutputStream> outputStreamSupplier) { this.docSupplier = docSupplier; this.outputStreamSupplier = outputStreamSupplier; } public String populateDoc(String text) { try (XWPFDocument doc = docSupplier.get(); OutputStream out = outputStreamSupplier.get()) { StringBuilder sb = new StringBuilder(); LOGGER.info("Document Created."); String[] lines = text.split("\n"); for (String line : lines) { XWPFParagraph para = doc.createParagraph(); XWPFRun run = para.createRun(); run.setText(line); sb.append(line); sb.append("\n"); } LOGGER.info("Document saved."); doc.write(out); return sb.toString(); } catch (IOException ioe) { LOGGER.error(ioe); } return null; } }
步骤2:编写正确的测试代码
现在可以MockXWPFDocument和OutputStream,让write方法无操作,同时避免空指针异常:
@ExtendWith(MockitoExtension.class) public class FileWriterTest { @Mock private XWPFDocument mockDoc; @Mock private OutputStream mockOut; @Test void testTextReturnsHello() throws IOException { // 注入Mock对象 FileWriter fileWriter = new FileWriter(() -> mockDoc, () -> mockOut); String text = "Hello\nWorld"; String expected = "Hello\nWorld\n"; // Mock后续调用避免NPE XWPFParagraph mockPara = Mockito.mock(XWPFParagraph.class); XWPFRun mockRun = Mockito.mock(XWPFRun.class); Mockito.when(mockDoc.createParagraph()).thenReturn(mockPara); Mockito.when(mockPara.createRun()).thenReturn(mockRun); // 让write方法无操作 Mockito.doNothing().when(mockDoc).write(mockOut); // 执行测试 String result = fileWriter.populateDoc(text); // 验证结果与方法调用 Assertions.assertEquals(expected, result); Mockito.verify(mockDoc).write(mockOut); Mockito.verify(mockRun, Mockito.times(2)).setText(Mockito.anyString()); } }
关键说明
- 依赖注入是前提:如果方法内部直接new对象,Mockito无法替换这些实例,必须通过Supplier或其他方式暴露依赖,方便测试注入Mock。
- Mock链调用:
createParagraph和createRun会返回对象,必须Mock这些方法的返回值,否则会抛出空指针异常。 - doNothing的正确用法:
write是void方法,doNothing().when(mockDoc).write(mockOut);是标准写法,之前的错误是因为Mock对象未被实际使用。
内容的提问来源于stack exchange,提问作者AceKokuren

