Android Espresso Dagger测试:构造注入下Mock Presenter的实现
你的场景我完全理解:RegistrationActivity通过构造注入拿到RegistrationPresenter,但Mock这个Presenter时,发现只有把Presenter放到Module里提供才能生效。这其实是因为Dagger的注入优先级——当有Module提供实例时会优先使用Module的逻辑,而构造注入的类在测试时需要我们通过测试组件或直接替换的方式,覆盖默认的注入逻辑。下面给你两种实用的解决方案:
方案一:构建测试专用的Dagger组件与Module
这种方式贴合Dagger原生逻辑,适合需要完整注入环境的测试场景:
创建Mock Presenter
你可以自定义Mock类继承原Presenter,也可以直接用Mockito生成Mock实例:// 方式1:自定义Mock类 public class MockRegistrationPresenter extends RegistrationPresenter { @Inject public MockRegistrationPresenter() { super(); } // 重写方法,返回测试需要的预设行为 @Override public void handleRegistration() { // 测试专属逻辑 } } // 方式2:用Mockito生成Mock(更灵活) // RegistrationPresenter mockPresenter = Mockito.mock(RegistrationPresenter.class);编写测试Module
这个Module的核心作用是把Presenter的绑定替换成Mock版本:@Module public class TestRegistrationModule { @Provides RegistrationPresenter provideRegistrationPresenter() { // 返回自定义Mock类或者Mockito生成的Mock实例 return new MockRegistrationPresenter(); // 或者 return Mockito.mock(RegistrationPresenter.class); } }创建测试组件
让测试组件继承原Application组件的接口,替换掉对应的Module:@Component(modules = {TestRegistrationModule.class, /* 原Application组件依赖的其他Module */}) public interface TestRegistrationComponent extends ApplicationComponent { void inject(RegistrationActivity activity); }在测试中替换Application组件
测试前把App里的原组件换成测试组件,这样Activity启动时就会自动注入Mock的Presenter:@RunWith(AndroidJUnit4.class) public class RegistrationActivityTest { @Rule public ActivityScenarioRule<RegistrationActivity> activityRule = new ActivityScenarioRule<>(RegistrationActivity.class); @Before public void setup() { SampleApplication app = (SampleApplication) InstrumentationRegistry.getInstrumentation() .getTargetContext().getApplicationContext(); // 构建测试组件 TestRegistrationComponent testComponent = DaggerTestRegistrationComponent.create(); // 需要在SampleApplication中添加setApplicationComponent方法,用来替换组件 app.setApplicationComponent(testComponent); } // 你的测试用例 @Test public void testRegistrationFlow() { // 执行测试逻辑,验证Mock Presenter的行为是否符合预期 } }
方案二:直接替换Activity中的Presenter字段(快速简单)
如果你的测试不需要完整的Dagger注入环境,直接给Activity里的Presenter字段赋值是最快捷的方式:
@RunWith(AndroidJUnit4.class) public class RegistrationActivityTest { @Mock RegistrationPresenter mockPresenter; @Before public void setup() { MockitoAnnotations.openMocks(this); // 启动Activity场景,替换Presenter字段 ActivityScenario<RegistrationActivity> scenario = ActivityScenario.launch(RegistrationActivity.class); scenario.onActivity(activity -> { // 如果字段是private的,可以用反射设置,或者把字段改成包访问权限 activity.registrationPresenter = mockPresenter; }); } @Test public void testRegisterButtonClick() { // 模拟点击注册按钮 // 验证Mock Presenter的方法是否被调用 Mockito.verify(mockPresenter).register(anyString(), anyString()); } }
注意:如果原Presenter字段是private的,要么改成包访问权限(测试目录下可直接访问),要么用反射工具类(比如Mockito的Whitebox)来设置值。
额外优化建议:面向接口编程
最好让RegistrationPresenter实现一个接口(比如RegistrationContract.Presenter),然后Activity中注入的是这个接口而非具体类。这样Mock时只需要Mock接口,不需要依赖具体实现,Dagger绑定也会更灵活:
// 定义接口 public interface RegistrationContract { interface Presenter { void register(String username, String password); } } // 原Presenter实现接口 public class RegistrationPresenter implements RegistrationContract.Presenter { @Inject public RegistrationPresenter() {} @Override public void register(String username, String password) { // 业务逻辑 } }
原Module中绑定接口到实现类,测试时只需要把接口的绑定换成Mock接口即可,扩展性更强。
内容的提问来源于stack exchange,提问作者Булат Мухутдинов

