在单元测试中Mock iText 7.1.1以规避商业许可证使用的可行性问询
在单元测试中Mock iText 7.1.1以规避商业许可证使用的可行性问询
当然可行!而且用Mockito这类主流Mock工具的话,完全不用费太大力气就能搞定——核心就是把iText的所有依赖对象都Mock掉,让测试只走你自己的业务逻辑,根本不会触发商业许可证的校验。
我给你梳理下具体的实现思路和代码示例,都是实际项目里常用的方案:
1. 先搞定Mock工具的准备
首先确保你的测试依赖里有JUnit 5 + Mockito,如果是Maven项目,pom.xml里加这些依赖(版本选你项目兼容的就行):
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>
另外,iText 7的很多核心类(比如PdfWriter)是final类,Mockito默认不能Mock final类,所以要开启它的inline Mock支持:在src/test/resources/mockito-extensions/目录下新建一个名为org.mockito.plugins.MockMaker的文件,里面只写一行:
mock-maker-inline
2. 测试代码的核心实现
你不需要大改业务代码,用Mockito的mockConstruction()(Mockito 3.5+支持)就能Mock iText类的构造函数,直接替换成Mock实例。下面是完整的测试示例:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.MockedConstruction; import org.mockito.junit.jupiter.MockitoExtension; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Paragraph; import java.io.ByteArrayOutputStream; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class PdfGenerationServiceTest { // 注入你的实际服务类 @InjectMocks private PdfGenerationService yourPdfService; @Test void createPdf_ShouldExecuteAllBusinessLogic_WithMockedIText() throws Exception { // Mock PdfWriter的构造函数,返回Mock实例 try (MockedConstruction<PdfWriter> mockedPdfWriter = mockConstruction(PdfWriter.class)) { // Mock PdfDocument的构造函数,同时设置close()啥也不做 try (MockedConstruction<PdfDocument> mockedPdfDoc = mockConstruction(PdfDocument.class, (mock, context) -> doNothing().when(mock).close())) { // Mock Document的构造函数,设置add()和close()的Mock行为 try (MockedConstruction<Document> mockedDocument = mockConstruction(Document.class, (mock, context) -> { doNothing().when(mock).add(any()); doNothing().when(mock).close(); })) { // Mock Paragraph的构造函数,链式方法要返回自身(因为iText的方法是fluent风格) try (MockedConstruction<Paragraph> mockedParagraph = mockConstruction(Paragraph.class, (mock, context) -> { when(mock.setFont(any())).thenReturn(mock); when(mock.setFontSize(anyFloat())).thenReturn(mock); when(mock.setMarginTop(anyFloat())).thenReturn(mock); })) { // 准备测试数据 PdfData testPdfData = new PdfData(); testPdfData.setSomeStr("Test Content"); // 调用要测试的方法 byte[] result = yourPdfService.createPdf(testPdfData); // 验证核心逻辑:比如Document的add方法被调用了一次 Document mockDoc = mockedDocument.constructed().get(0); verify(mockDoc, times(1)).add(any(Paragraph.class)); // 验证Paragraph的属性设置是否都执行了 Paragraph mockPara = mockedParagraph.constructed().get(0); verify(mockPara, times(1)).setFont(any()); verify(mockPara, times(1)).setFontSize(anyFloat()); verify(mockPara, times(1)).setMarginTop(anyFloat()); // 如果你需要验证返回的字节数组,因为我们Mock了流操作,这里会是空数组,根据你的需求调整 } } } } } }
3. 关键细节说明
- Fluent风格方法的Mock:iText的很多方法(比如
setFont()、setFontSize())是链式调用,所以Mock的时候要让这些方法返回Mock实例本身,不然业务代码里的链式调用会报NPE。 - 嵌套Mock的简化:如果觉得多层
try-with-resources太啰嗦,可以把这些Mock逻辑抽成一个@BeforeEach方法,或者用Mockito的MockedConstruction的批量Mock(不过多层嵌套更清晰)。 - 覆盖所有iText对象:如果你的代码里用到了
Div或者其他iText元素,只需要给它们加对应的mockConstruction()就行,逻辑和Paragraph完全一致。 - 许可证完全不触发:因为所有iText的实例都是Mock的,根本不会走到iText的实际代码,所以完全不会触发商业许可证的校验逻辑。
最后总结
完全不需要花太多精力就能实现!这个方案的核心就是用Mockito的构造函数Mock能力,把所有iText的依赖都替换成Mock对象,让测试完整走一遍你的业务代码,同时验证你对iText API的调用是否符合预期——既覆盖了代码,又规避了商业许可证的问题。
内容来源于stack exchange
相关产品推荐
相关产品推荐

