让Dagger在为Android进行espresso功能测试时注入模拟对象

Kau*_*pal 17 android dependency-injection functional-testing dagger android-espresso

我最近和Dagger一起吃饱了,因为DI的概念完全合情合理.DI的一个更好的"副产品"(杰克沃顿在他的一个演讲中提出)更易于测试.

所以现在我基本上使用espresso做一些功能测试,我希望能够将虚拟/模拟数据注入应用程序并让活动显示出来.我猜是因为,这是DI的最大优势之一,这应该是一个相对简单的问题.出于某种原因,我似乎无法绕过它.任何帮助将非常感激.这是我到目前为止(我写了一个反映我当前设置的例子):

public class MyActivity
    extends MyBaseActivity {

    @Inject Navigator _navigator;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyApplication.get(this).inject(this);

        // ...

        setupViews();
    }

    private void setupViews() {
        myTextView.setText(getMyLabel());
    }

    public String getMyLabel() {
        return _navigator.getSpecialText(); // "Special Text"
    }
}
Run Code Online (Sandbox Code Playgroud)

这些是我的匕首模块:

// Navigation Module

@Module(library = true)
public class NavigationModule {

    private Navigator _nav;

    @Provides
    @Singleton
    Navigator provideANavigator() {
        if (_nav == null) {
            _nav = new Navigator();
        }
        return _nav;
    }
}

// App level module

@Module(
    includes = { SessionModule.class, NavigationModule.class },
    injects = { MyApplication.class,
                MyActivity.class,
                // ...
})
public class App {
    private final Context _appContext;
    AppModule(Context appContext) {
        _appContext = appContext;
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

在我的Espresso测试中,我试图插入一个模拟模块,如下所示:

public class MyActivityTest
    extends ActivityInstrumentationTestCase2<MyActivity> {

    public MyActivityTest() {
        super(MyActivity.class);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        ObjectGraph og = ((MyApplication) getActivity().getApplication()).getObjectGraph().plus(new TestNavigationModule());
        og.inject(getActivity());
    }

    public void test_SeeSpecialText() {
        onView(withId(R.id.my_text_view)).check(matches(withText(
            "Special Dummy Text")));
    }

    @Module(includes = NavigationModule.class,
            injects = { MyActivityTest.class, MyActivity.class },
            overrides = true,
            library = true)
    static class TestNavigationModule {

        @Provides
        @Singleton
        Navigator provideANavigator() {
            return new DummyNavigator(); // that returns "Special Dummy Text"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这根本不起作用.我的espresso测试运行,但TestNavigationModule完全被忽略... arr ...... :(

我究竟做错了什么?有没有更好的方法来使用Espresso模拟模块.我搜索并看过正在使用的Robolectric,Mockito等的例子.但我只想要纯Espresso测试,需要用我的模拟替换模块.我该怎么做?

编辑:

所以我使用@ user3399328方法获得静态测试模块列表定义,检查null然后在我的Application类中添加它.我仍然没有得到我的Test注入版本的类.我有一种感觉,它可能与匕首测试模块定义有关,而不是我的espresso生命周期.我做出假设的原因是我添加了调试语句,并发现静态测试模块在应用程序类中注入时是非空的.你能指点一下我可能做错的方向吗?以下是我的定义的代码片段:

我的应用程序:

@Override
public void onCreate() {
    // ...
    mObjectGraph = ObjectGraph.create(Modules.list(this));
    // ...   
}
Run Code Online (Sandbox Code Playgroud)

模块:

public class Modules {

    public static List<Object> _testModules = null;

    public static Object[] list(MyApplication app) {
        //        return new Object[]{ new AppModule(app) };
        List<Object> modules = new ArrayList<Object>();
        modules.add(new AppModule(app));

        if (_testModules == null) {
            Log.d("No test modules");
        } else {
            Log.d("Test modules found");
        }

        if (_testModules != null) {
            modules.addAll(_testModules);
        }

        return modules.toArray();
    }
}   
Run Code Online (Sandbox Code Playgroud)

我的测试类中修改过的测试模块:

@Module(overrides = true, library = true)
public static class TestNavigationModule {

    @Provides
    @Singleton
    Navigator provideANavigator()() {
        Navigator navigator = new Navigator();
        navigator.setSpecialText("Dummy Text");
        return navigator;
    }
}
Run Code Online (Sandbox Code Playgroud)

pme*_*aho 9

随着Dagger 2和Espresso 2的确有所改善.这就是测试用例现在的样子.请注意,ContributorsModel由Dagger提供.这里有完整的演示:https://github.com/pmellaaho/RxApp

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

ContributorsModel mModel;

@Singleton
@Component(modules = MockNetworkModule.class)
public interface MockNetworkComponent extends RxApp.NetworkComponent {
}

@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
        MainActivity.class,
        true,     // initialTouchMode
        false);   // launchActivity.

@Before
public void setUp() {
    Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
    RxApp app = (RxApp) instrumentation.getTargetContext()
            .getApplicationContext();

    MockNetworkComponent testComponent = DaggerMainActivityTest_MockNetworkComponent.builder()
            .mockNetworkModule(new MockNetworkModule())
            .build();
    app.setComponent(testComponent);
    mModel = testComponent.contributorsModel();
}

@Test
public void listWithTwoContributors() {

    // GIVEN
    List<Contributor> tmpList = new ArrayList<>();
    tmpList.add(new Contributor("Jesse", 600));
    tmpList.add(new Contributor("Jake", 200));

    Observable<List<Contributor>> testObservable = Observable.just(tmpList);

    Mockito.when(mModel.getContributors(anyString(), anyString()))
            .thenReturn(testObservable);

    // WHEN
    mActivityRule.launchActivity(new Intent());
    onView(withId(R.id.startBtn)).perform(click());

    // THEN
    onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 0))
            .check(matches(hasDescendant(withText("Jesse"))));

    onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 0))
            .check(matches(hasDescendant(withText("600"))));

    onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 1))
            .check(matches(hasDescendant(withText("Jake"))));

    onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 1))
            .check(matches(hasDescendant(withText("200"))));
}
Run Code Online (Sandbox Code Playgroud)

  • 这也是我发现的最佳方式.1)在您的应用程序上公开DI容器2)使ActivityTestRule不自动启动您的应用程序3)在测试方法(或设置)中更改DI容器4)手动启动您的应用程序5)测试. (2认同)

use*_*328 8

你的方法不起作用,因为它只发生一次,正如Matt所提到的,当活动的真实注入代码运行时,它将消除你的特殊对象图注入的任何变量.

有两种方法可以让它发挥作用.

快速方法:在您的活动中创建一个公共静态变量,以便测试可以分配覆盖模块,并且如果实际活动代码不为空(仅在测试中发生),则实际活动代码始终包含此模块.它类似于我的答案在这里只为你的活动基础类,而不是应用程序.

更长,可能更好的方法:重构代码,以便所有活动注入(更重要的是图创建)发生在一个类中,类似于ActivityInjectHelper.在您的测试包中,创建另一个名为ActivityInjectHelper的类,其具有完全相同的包路径,该路径实现相同的方法,除了您的测试模块.因为首先加载测试类,所以应用程序将使用测试ActivityInjectHelper执行.再次,这与我在这里的答案类似,仅针对另一个班级.

更新:

我看到你发布了更多的代码,它接近工作,但没有雪茄.对于活动和应用程序,测试模块需要在onCreate()运行之前进入.在处理活动对象图时,在测试的getActivity()之前的任何时候都可以.在处理应用程序时,它有点困难,因为在setUp()运行时已经调用了onCreate().幸运的是,在测试的构造函数中进行操作 - 此时尚未创建应用程序.我在第一个链接中简要提到了这一点.