Android中Mock Toast行为时Fragment上下文绑定问题
解决Fragment测试中requireContext()的Mock问题
问题根源
你遇到的错误是因为requireContext()是Fragment类的final方法,Mockito默认无法mock final方法。当你调用when(fragment.requireContext()).thenReturn(mockContext)时,实际上触发了Fragment的真实requireContext()方法——而此时Fragment并未attach到任何上下文,因此抛出"not attached to a context"异常。
解决方案
方案1:启用Mockito的Final方法Mock支持
Mockito通过mock-maker-inline扩展可以支持mock final方法,步骤如下:
- 在测试资源目录下创建路径:
src/test/resources/mockito-extensions - 新建文件
org.mockito.plugins.MockMaker,文件内容仅一行:mock-maker-inline - 修改测试代码,用spy包装真实Fragment实例以支持final方法mock:
public void testMetRequirements() { InSweFragment fragment = spy(new InSweFragment()); mockContext = mock(Context.class); // 现在可以mock final方法requireContext() doReturn(mockContext).when(fragment).requireContext(); // 剩余测试逻辑不变 when(mockA67Text.getText().toString()).thenReturn("90"); when(mockA08Text.getText().toString()).thenReturn("85"); when(mockA48Text.getText().toString()).thenReturn("90"); when(mockA22Text.getText().toString()).thenReturn("80"); when(mockA31Text.getText().toString()).thenReturn("85"); when(mockA37Text.getText().toString()).thenReturn("75"); fragment.calculateGrade(mockA67Text, mockA08Text, mockA48Text, mockA22Text, mockA31Text, mockA37Text); verify(mockToast).makeText(mockContext, "You have met the requirements for this POSt!", Toast.LENGTH_LONG, R.style.passtoast); }
方案2:重构代码解耦Context依赖
更优雅的方式是将Context依赖从Fragment中解耦,避免直接在业务逻辑中调用requireContext():
- 在
InSweFragment中添加测试可见的Context设置方法:import androidx.annotation.VisibleForTesting; public class InSweFragment extends Fragment { private Context mContext; @Override public void onAttach(@NonNull Context context) { super.onAttach(context); mContext = context; } // 测试专用方法,设置上下文 @VisibleForTesting public void setContextForTesting(Context context) { mContext = context; } public void calculateGrade(EditText a67, EditText a08, ...) { // 替换所有requireContext()为mContext if (/* 条件判断 */) { StyleableToast.makeText(mContext, "...", ...).show(); } } } - 测试代码中直接设置测试上下文,无需mock:
public void testMetRequirements() { InSweFragment fragment = new InSweFragment(); mockContext = mock(Context.class); fragment.setContextForTesting(mockContext); // 剩余测试逻辑不变 when(mockA67Text.getText().toString()).thenReturn("90"); // ...其他EditText设置 fragment.calculateGrade(mockA67Text, mockA08Text, mockA48Text, mockA22Text, mockA31Text, mockA37Text); verify(mockToast).makeText(mockContext, "You have met the requirements for this POSt!", Toast.LENGTH_LONG, R.style.passtoast); }
方案3:使用AndroidX Test的FragmentScenario模拟真实生命周期
如果希望测试更贴近真实场景,可以用FragmentScenario将Fragment attach到测试上下文:
import androidx.fragment.app.testing.FragmentScenario; import org.junit.Test; public void testMetRequirements() { // 启动Fragment并attach到测试上下文 FragmentScenario<InSweFragment> scenario = FragmentScenario.launchInContainer(InSweFragment.class); scenario.onFragment(fragment -> { // 在Fragment已attach的状态下执行测试逻辑 when(mockA67Text.getText().toString()).thenReturn("90"); when(mockA08Text.getText().toString()).thenReturn("85"); when(mockA48Text.getText().toString()).thenReturn("90"); when(mockA22Text.getText().toString()).thenReturn("80"); when(mockA31Text.getText().toString()).thenReturn("85"); when(mockA37Text.getText().toString()).thenReturn("75"); fragment.calculateGrade(mockA67Text, mockA08Text, mockA48Text, mockA22Text, mockA31Text, mockA37Text); verify(mockToast).makeText(fragment.requireContext(), "You have met the requirements for this POSt!", Toast.LENGTH_LONG, R.style.passtoast); }); }
补充:关于StyleableToast的Mock
如果StyleableToast.makeText是静态方法,需要使用Mockito的静态Mock功能来验证:
try (MockedStatic<StyleableToast> mockedStatic = Mockito.mockStatic(StyleableToast.class)) { // 模拟静态方法调用 mockedStatic.when(() -> StyleableToast.makeText(any(Context.class), anyString(), anyInt(), anyInt())) .thenReturn(mock(StyleableToast.class)); // 执行测试逻辑 fragment.calculateGrade(...); // 验证静态方法调用 mockedStatic.verify(() -> StyleableToast.makeText(mockContext, "You have met the requirements for this POSt!", Toast.LENGTH_LONG, R.style.passtoast)); }
内容的提问来源于stack exchange,提问作者Shak Jivraj
相关产品推荐
相关产品推荐

