单元测试中如何Mock ServletContext以访问WAR资源并加载Freemarker模板?
我明白你遇到的困境——生产环境里依托ServletContext加载WAR内的Freemarker模板很顺畅,但单元测试时没有容器提供的ServletContext实例,确实会卡壳。下面给你几个实用的解决方案,包括你提到的Spring MockServletContext的正确用法,以及更轻量的替代方案:
方案1:单独使用Spring的MockServletContext(无需完整Spring框架)
你完全可以单独用MockServletContext,不需要引入整个Spring生态,只要在测试依赖里加入spring-test就行。之前没成功大概率是因为资源路径配置不对,毕竟单元测试的资源根目录和WAR的结构不一样。
举个具体的例子:
假设你的Freemarker模板在WAR的WEB-INF/templates/目录下,打包后这些文件会被放到类路径的WEB-INF/templates/下。单元测试时,你可以这样配置:
import org.springframework.mock.web.MockServletContext; import freemarker.template.Configuration; // 创建MockServletContext,指定资源根路径(对应WAR的根目录,单元测试里一般是src/main/webapp) MockServletContext mockServletContext = new MockServletContext("src/main/webapp"); Configuration freemarkerConfig = new Configuration(Configuration.VERSION_2_3_31); // 传入MockServletContext,指定模板所在的相对路径 freemarkerConfig.setServletContextForTemplateLoading(mockServletContext, "/WEB-INF/templates"); // 接下来就可以正常获取模板了 Template template = freemarkerConfig.getTemplate("test.ftl");
注意:如果你的测试资源放在src/test/resources下,也可以调整MockServletContext的构造参数,指向测试资源目录,确保能找到模板文件。
方案2:改用ClassTemplateLoader(完全脱离ServletContext)
其实WAR里的模板文件最终会被打包到类路径中(比如WEB-INF/classes/WEB-INF/templates),单元测试时完全可以用Freemarker自带的ClassTemplateLoader直接从类路径加载模板,根本不需要ServletContext,这是更轻量的方案。
代码示例:
import freemarker.template.Configuration; import freemarker.template.ClassTemplateLoader; Configuration freemarkerConfig = new Configuration(Configuration.VERSION_2_3_31); // 指定模板在类路径下的位置,比如"/WEB-INF/templates" ClassTemplateLoader loader = new ClassTemplateLoader(getClass(), "/WEB-INF/templates"); freemarkerConfig.setTemplateLoader(loader); // 加载模板 Template template = freemarkerConfig.getTemplate("test.ftl");
这个方案的好处是不需要任何Servlet相关的依赖,单元测试更纯粹,也避免了Mock容器的麻烦。
方案3:用Mockito Mock ServletContext(灵活定制行为)
如果你的代码逻辑必须依赖ServletContext(比如有其他和ServletContext相关的操作),可以用Mockito来Mock一个ServletContext实例,而不是用Spring的实现。
示例代码:
import org.mockito.Mockito; import javax.servlet.ServletContext; import freemarker.template.Configuration; // Mock ServletContext ServletContext mockServletContext = Mockito.mock(ServletContext.class); // 模拟getResourceAsStream方法,返回模板文件的输入流 Mockito.when(mockServletContext.getResourceAsStream("/WEB-INF/templates/test.ftl")) .thenReturn(getClass().getResourceAsStream("/WEB-INF/templates/test.ftl")); Configuration freemarkerConfig = new Configuration(Configuration.VERSION_2_3_31); freemarkerConfig.setServletContextForTemplateLoading(mockServletContext, "/WEB-INF/templates"); // 正常使用模板 Template template = freemarkerConfig.getTemplate("test.ftl");
这种方式可以灵活定制ServletContext的各种行为,适合需要模拟复杂场景的测试。
内容的提问来源于stack exchange,提问作者pbuchheit

