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

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方法,步骤如下:

  1. 在测试资源目录下创建路径:src/test/resources/mockito-extensions
  2. 新建文件org.mockito.plugins.MockMaker,文件内容仅一行:
    mock-maker-inline
    
  3. 修改测试代码,用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():

  1. 在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();
            }
        }
    }
    
  2. 测试代码中直接设置测试上下文,无需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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 04:07:12