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

测试时Stub Apache POI的XWPFDocument.write方法避免生成实体文档

问题:测试Apache POI写Word方法时避免生成实体文档

我有一个用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());
    }
}

关键说明

  1. 依赖注入是前提:如果方法内部直接new对象,Mockito无法替换这些实例,必须通过Supplier或其他方式暴露依赖,方便测试注入Mock。
  2. Mock链调用:createParagraph和createRun会返回对象,必须Mock这些方法的返回值,否则会抛出空指针异常。
  3. doNothing的正确用法:write是void方法,doNothing().when(mockDoc).write(mockOut);是标准写法,之前的错误是因为Mock对象未被实际使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 08:25:21