如何Mock FacesContext ExternalContext返回的Request对象并解决空指针问题
问题原因
- 第11行报空指针说明
context变量为null,也就是第10行的FacesContext.getCurrentInstance().getExternalContext()返回了null,本质是你定义的Mock规则没有生效。 - 常见诱因1:Mockito注解未正确激活。你用
@InjectMocks声明了测试对象,但是如果没有激活Mockito注解(JUnit4没加@RunWith(MockitoJUnitRunner.class)、JUnit5没加@ExtendWith(MockitoExtension.class),也没有手动调用MockitoAnnotations.openMocks(this)),手动创建的mock对象和测试类的绑定关系不会生效。 - 常见诱因2:执行顺序错误。如果在测试对象初始化后才设置FacesContext的mock规则,或者测试执行前已有逻辑提前调用了FacesContext相关方法,就会拿到null值。
- 潜在兼容问题:你写的
FacesContextProvider调用的setCurrentInstance是FacesContext的protected静态方法,部分JSF实现(比如MyFaces)对该方法的封装不同,可能导致设值不生效。
解决方案
先激活Mockito注解
根据你用的JUnit版本选择对应配置:- JUnit4:在测试类上加
@RunWith(MockitoJUnitRunner.class) - JUnit5:在测试类上加
@ExtendWith(MockitoExtension.class)
也可以直接在测试前置方法中手动激活:
@BeforeEach void setup() { MockitoAnnotations.openMocks(this); }- JUnit4:在测试类上加
调整测试代码执行顺序,确保mock规则在测试方法执行前完成设置
调整后的测试代码示例:// JUnit5用@ExtendWith(MockitoExtension.class),JUnit4换@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class) public class MainBackingTest { @InjectMocks private MainBacking mainBackingUnderTest; private FacesContext mockFacesContext; private ExternalContext mockExternalContext; private HttpServletRequest mockRequest; @BeforeEach void setupMock() { // 每次测试前初始化全新的mock实例 mockFacesContext = FacesContextProvider.getCurrentInstance(); mockExternalContext = Mockito.mock(ExternalContext.class); mockRequest = Mockito.mock(HttpServletRequest.class); // 绑定mock规则 when(mockFacesContext.getExternalContext()).thenReturn(mockExternalContext); when(mockExternalContext.getRequest()).thenReturn(mockRequest); } @AfterEach void clearMock() { // 测试结束后清理FacesContext实例,避免影响其他测试 if (mockFacesContext != null) { mockFacesContext.release(); } } @Test void testInit() { mainBackingUnderTest.init(); // 后续添加断言逻辑 } }兼容不同JSF实现,修改FacesContextProvider用反射强制设置实例
替换后的Provider代码如下:public abstract class FacesContextProvider extends FacesContext { private FacesContextProvider() {} private static final Release RELEASE = new Release(); private static class Release implements Answer<Void> { @Override public Void answer(InvocationOnMock invocation) throws Throwable { clearCurrentInstance(); return null; } } public static FacesContext getCurrentInstance() { FacesContext context = Mockito.mock(FacesContext.class); setCurrentInstance(context); Mockito.doAnswer(RELEASE).when(context).release(); return context; } // 反射强制设置线程绑定的FacesContext实例,兼容所有JSF实现 private static void setCurrentInstance(FacesContext context) { try { Field field = FacesContext.class.getDeclaredField("currentInstance"); field.setAccessible(true); ThreadLocal<FacesContext> threadLocal = (ThreadLocal<FacesContext>) field.get(null); threadLocal.set(context); } catch (Exception e) { throw new RuntimeException("设置Mock FacesContext失败", e); } } private static void clearCurrentInstance() { try { Field field = FacesContext.class.getDeclaredField("currentInstance"); field.setAccessible(true); ThreadLocal<FacesContext> threadLocal = (ThreadLocal<FacesContext>) field.get(null); threadLocal.remove(); } catch (Exception e) { throw new RuntimeException("清理Mock FacesContext失败", e); } } }
内容的提问来源于stack exchange,提问作者muhkanda
相关产品推荐
相关产品推荐

