android - 如何使用 Espresso 在 Instrumentation 测试中注入(inject) Mocked Presenter of Activity

标签 android junit android-espresso dagger-2 android-mvp

我已经试了一个星期了。我已经抓取了所有可用的文章,但它们的实现或示例不尽如人意或停留在 Espresso 测试的步骤。

我的 Android 应用程序遵循 MVP 架构(并且是在 Java 中)

场景:[仅举一个例子] 我有一个使用 Dagger2 获取 HomePresenterHomeActivity。 (在通过 HomeComponent 中的 void inject(HomeActivity activity) 公开的 HomeModule 中提供方法。

在我的 HomeActivity 的 espressoTest 中,我想注入(inject)一个 mockpresent。 我没有通过 AppComponentAppModule 中公开此依赖项。网上的大多数例子都是这样做的(所以他们只是创建一个新的 testApplication 然后做需要的)

我不想使用 productFlavours 方式注入(inject)或提供模拟类,因为它无法让我控制 Mockito.when 方法。

所以基本上。我想注入(inject)一个 mockpresenter,为了在 espresso 中进行单元测试,我可以在其中执行任何 Mockito.when() 操作。

我的代码如下。

主页组件

@HomeScope
@Component(modules = HomeModule.class,dependencies = AppComponent.class)
public interface HomeComponent {
    void inject(HomeActivity activity);
}

家庭模块

@Module
public class HomeModule {

    private final IHomeContract.View view;

    public HomeModule(IHomeContract.View view) {
        this.view = view;
    }

    @Provides
    @HomeScope
    public IHomeContract.Presenter presenter(FlowsRepository flowsRepository, UserRepository userRepository, LoanRepository loanRepository) {
        return new HomePresenter(view, flowsRepository, userRepository, loanRepository);
    }

}

应用组件

@Component(modules = {AppModule.class,RepositoryModule.class})
@AppScope
public interface AppComponent {
    void inject(App app);

    FlowsRepository flowRepository();
    LoanRepository loanRepository();
    UserRepository userRepository();
}

应用模块

@Module
public class AppModule {
    private Context appContext;

    public AppModule(@NonNull Context context) {
        this.appContext = context;
    }

    @Provides
    @AppScope
    public Context context() {
        return appContext;
    }
}

应用

component = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();
        component.inject(this);

主页 Activity

HomeComponent component = DaggerHomeComponent.builder()
                .appComponent(((App) getApplication()).getComponent())
                .homeModule(new HomeModule(this))
                .build();

再来一次。在我的测试(espresso)中,我想注入(inject)一个由 Mockito 设置的 mockedHomePresenter。所以我可以对我的观点进行单元测试。

最佳答案

解决问题的关键点是有这样一个 Dagger 模块,它在 HomeActivity 的插桩测试中提供模拟 Presenter的“真正的”。

为此,需要完成以下 2 个额外操作(您可能还希望看到 example )。

  1. HomeActivityComponent 的实例化委托(delegate)给一些抽象。
  2. 在插桩测试中替换抽象的实现以提供模拟。

我将在下面的示例中使用 Kotlin

定义委托(delegate)接口(interface):

interface HomeComponentBuilder {
    fun build(view: IHomeContract.View): HomeComponent
}

HomeComponent 初始化从 HomeActivity 移至委托(delegate)实现:

class HomeComponentBuilderImpl constructor(private val app: App) : HomeComponentBuilder {

override fun build(view: IHomeContract.View): HomeComponent =
    DaggerHomeComponent.builder()
        .homeModule(HomeModule(view))
        .build()
}

使委托(delegate)处于应用程序“范围”中,以便您可以为仪器测试交换其实现:

interface App {
    val homeComponentBuilder: HomeComponentBuilder
    ...
}

App 实现现在应该包含

class AppImpl : Application(), App {
    override val homeComponentBuilder: HomeComponentBuilder by lazy {
        HomeComponentBuilderImpl(this@AppImpl)
    }
    ...
}

HomeActivity 中的组件初始化如下所示:

(application as App)
        .homeComponentBuilder
        .build(this)
        .inject(this)

对于仪器测试,创建扩展 HomeComponentTestHomeComponent:

@HomeScope
@Component(modules = [TestHomeModule::class])
interface TestHomeComponent : HomeComponent

TestHomeModule 提供了一个模拟Presenter

@Module
class TestHomeModule {

    @Provides
    fun providePresenter(): IHomeContract.Presenter = mock()
}

剩下要做的就是进行测试委托(delegate)实现

class TestHomeComponentBuilderImpl : HomeComponentBuilder {
    override fun build(view: IHomeContract.View): HomeComponent =
        DaggerTestHomeComponent.builder()
             .testTestHomeModule(TestHomeModule())
             .build()
}

并在 TestAppImpl 中初始化它

class TestAppImpl : Application(), App {
    override val homeComponentBuilder: HomeComponentBuilder by lazy {
        TestHomeComponentBuilderImpl()
    }
    ...
}

其余的都是标准的。创建一个使用 TestAppImpl 的自定义 AndroidJUnitRunner:

class TestAppRunner : AndroidJUnitRunner() {
    override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application = Instrumentation.newApplication(TestAppImpl::class.java, context)
}

并将其添加到app模块build.gradle

defaultConfig {
    testInstrumentationRunner "your.package.TestAppRunner"
    ...
}

使用示例:

@RunWith(AndroidJUnit4::class)
class HomeActivityTest {
    private lateinit var mockPresenter: IHomeContract.Presenter

    @get:Rule
    val activityRule = ActivityTestRule(HomeActivity::class.java)

    @Before
    fun setUp() {
        mockPresenter = activityRule.activity.presenter
    }

    @Test
    fun activity_onCreate_presenter_should_onViewCreated() {
        verify(mockPresenter).someMethod()
    }
}

关于android - 如何使用 Espresso 在 Instrumentation 测试中注入(inject) Mocked Presenter of Activity,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51110120/

相关文章:

java - Android:从特定线程执行方法

Android Studio如何测试多线程?

android - 如何在没有 IdlingResource 的情况下在 Espresso 中等待异步任务

android - Facebook android sdk 4.0.1 - 未调用回调

libs 和 jniLibs 的 Android Studio 依赖问题

android - RecyclerView 的问题

android - 如何减小自定义贴纸应用程序的文件大小

java - 如何正确测试java中类的内部方法

java - JUnit 测试因 Jenkins 从属设备超时而失败

Android espresso 测试锁定和解锁屏幕