做ES preSSO功能测试,为Android的时候得到匕首注入mock对象 [英] getting Dagger to inject mock objects when doing espresso functional testing for Android

查看:154
本文介绍了做ES preSSO功能测试,为Android的时候得到匕首注入mock对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近去了全猪用匕首,因为DI的概念使得完整意义上的。其中一个副产品DI(如杰克沃顿放在自己presentations一个)是比较容易可测性。的更好

所以我现在基本上都采用ES preSSO做一些功能测试,我希望能够为虚拟/模拟数据注入到应用程序,并有活动给他们了。我猜,因为,这是DI的最大优势之一,这应该是一个相对简单的问。出于某种原因,不过,我似乎无法环绕它我的头。任何帮助将是非常美联社preciated。这是我迄今(我已经写了反映我的当前设置为例):

 公共类MyActivity
    扩展MyBaseActivity {

    @注入导航_navigator;

    @覆盖
    公共无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        MyApplication.get(本).inject(本);

        // ...

        setupViews();
    }

    私人无效setupViews(){
        myTextView.setText(getMyLabel());
    }

    公共字符串getMyLabel(){
        返回_navigator.getSpecialText(); //特殊文本
    }
}
 

这些都是我的匕首模块:

  //导航模块

@Module(库=真)
公共类NavigationModule {

    私人导航_nav;

    @Provides
    @Singleton
    导航provideANavigator(){
        如果(_nav == NULL){
            _nav =新的浏览器();
        }
        返回_nav;
    }
}

//程序级的模块

@Module(
    包括= {SessionModule.class,NavigationModule.class},
    注入= {MyApplication.class,
                MyActivity.class,
                // ...
})
公共类应用{
    私人最终语境_appContext;
    的AppModule(上下文appContext){
        _appContext = appContext;
    }
    // ...
}
 

在我的长者preSSO测试,我试图插入一个模拟模块,像这样:

 公共类MyActivityTest
    扩展ActivityInstrumentationTestCase2< MyActivity> {

    公共MyActivityTest(){
        超(MyActivity.class);
    }

  @覆盖
  公共无效设置()抛出异常{
      super.setUp();
      ObjectGraph OG =((MyApplication的)getActivity()getApplication()。)getObjectGraph()加(新TestNavigationModule())。。;
      og.inject(getActivity());
  }

    公共无效test_SeeSpecialText(){
        OnView为(withId(R.id.my_text_view))。检查(火柴(withText(
            特殊的虚拟文本)));
    }

    @Module(包括= NavigationModule.class,
            注入= {MyActivityTest.class,MyActivity.class},
            覆盖=真,
            库=真)
    静态类TestNavigationModule {

        @Provides
        @Singleton
        导航provideANavigator(){
            返回新DummyNavigator(); //返回特殊的虚拟文本
        }
    }
}
 

这是不工作的。我ES preSSO测试运行,但TestNavigationModule完全被忽略... ARR ...:(

我究竟做错了什么?有没有更好的方式来嘲笑模块用长者preSSO。我已经搜索并看到Robolectric,的Mockito等的例子被使用。但我只想单纯居preSSO测试,需要换出一个模块与我的模拟之一。我应该怎么做呢?

编辑:

所以我就跟着@ user3399328有一个静态的测试模块列表定义,检查空,然后在我的应用程序类将它添加的方法。我仍然没有得到我的测试注入的类版本虽然。我有一种感觉,虽然,其可能出错了匕首测试模块的定义,而不是我上课preSSO生命周期。我正在做假设的原因是,我添加调试语句,发现静态测试模块非空,在注射的应用程序类的时间。你可以点我的东西我可能是做错了方向。下面是我的定义,code片段:

MyApplication的:

  @覆盖
公共无效的onCreate(){
    // ...
    mObjectGraph = ObjectGraph.create(Modules.list(本));
    // ...
}
 

模块:

 公共类模块{

    公共静态列表<对象> _testModules = NULL;

    公共静态对象[]列表(MyApplication的应用程序){
        //返回新对象[] {新的AppModule(APP)};
        名单<对象>模块=新的ArrayList<对象>();
        modules.add(新的AppModule(应用程序));

        如果(_testModules == NULL){
            Log.d(未检测模块);
        } 其他 {
            Log.d(测试模块发现);
        }

        如果(_testModules!= NULL){
            modules.addAll(_testModules);
        }

        返回modules.toArray();
    }
}
 

在我的测试类修改测试模块:

  @Module(覆盖=真,图书馆= TRUE)
公共静态类TestNavigationModule {

    @Provides
    @Singleton
    导航provideANavigator()(){
        导航仪导航仪=新的浏览器();
        navigator.setSpecialText(虚拟文本);
        返回导航仪;
    }
}
 

解决方案

您的方法是行不通的,因为它只会发生一次,马特提到,当活动的真正注入code运行时,它会消灭任何变量注入您的特殊对象图。

有两种方法可以得到这个工作。

快捷方式:使你的活动一个公共静态变量,以便测试可以分配一个覆盖模块,并具有实际活动code总是包含这个模块,如果它不是空(这只会发生在测试)。它类似于我的答案<一href="https://groups.google.com/forum/#!searchin/android-test-kit-discuss/override%2420module/android-test-kit-discuss/0od5fliYJp4/Lw0ZYs1f9acJ">here只为您的活动的基类,而不是应用程序。

的时间越长,可能是更好的方法:重构code,使所有活动注入(更重要的是图形创作)发生在一个类,像ActivityInjectHelper。在您的测试包,创建另一个名为ActivityInjectHelper与完全一样包实现同样的方法路径类,但也加分测试的模块。由于测试类第一次加载,应用程序将与测试ActivityInjectHelper执行。同样它类似于我的答案<一href="https://groups.google.com/forum/#!searchin/android-test-kit-discuss/application/android-test-kit-discuss/DyJXrmycnV8/5kMpyrsBYlsJ">here只是不同的类。

更新:

我看到你贴得更code和它靠近工作,但没有雪茄。对于这两种活动和应用中,测试模块需要的onCreate之前悄悄在()中运行。当活动对象图处理,测试的getActivity前任何时间()的罚款。当应用程序处理,这是一个有点难度,因为的onCreate()已经被调用的时候设置()运行。幸运的是,这样做在测试的构造函数的工作 - 该应用程序还没有建立在这一点上。我简要地提到这一点,在我的第一个环节。

I've recently gone whole-hog with Dagger because the concept of DI makes complete sense. One of the nicer "by-products" of DI (as Jake Wharton put in one of his presentations) is easier testability.

So now i'm basically using espresso to do some functional testing, and i want to be able to inject dummy/mock data to the application and have the activity show them up. I'm guessing since, this is one of the biggest advantages of DI, this should be a relatively simple ask. For some reason though, I can't seem to wrap my head around it. Any help would be much appreciated. Here's what i have so far (I've written up an example that reflects my current setup):

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"
    }
}

These are my dagger modules:

// 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;
    }
    // ...
}

In my Espresso Test, I'm trying to insert a mock module like so:

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"
        }
    }
}

This is not working at all. My espresso tests run, but the TestNavigationModule is completely ignored... arr... :(

What am i doing wrong? Is there a better approach to mocking modules out with Espresso. I've searched and seen examples of Robolectric, Mockito etc. being used. But I just want pure Espresso tests and need to swap out a module with my mock one. How should i be doing this?

EDIT:

So i went with @user3399328 approach of having a static test module list definition, checking for null and then adding it in my Application class. I'm still not getting my Test injected version of the class though. I have a feeling though, its probably something wrong with dagger test module definition, and not my espresso lifecycle. The reason i'm making the assumption is that i add debug statements and find that the static test module is non-empty at time of injection in the application class. Could you point me to a direction of what i could possibly be doing wrong. Here are code snippets of my definitions:

MyApplication:

@Override
public void onCreate() {
    // ...
    mObjectGraph = ObjectGraph.create(Modules.list(this));
    // ...   
}

Modules:

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();
    }
}   

Modified test module within my test class:

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

    @Provides
    @Singleton
    Navigator provideANavigator()() {
        Navigator navigator = new Navigator();
        navigator.setSpecialText("Dummy Text");
        return navigator;
    }
}

解决方案

Your approach doesn't work because it only happens once, and as Matt mentioned, when the activity's real injection code runs, it will wipe out any variables injected by your special object graph.

There are two ways to get this to work.

The quick way: make a public static variable in your activity so a test can assign an override module and have the actual activity code always include this module if it's not null (which will only happen in tests). It's similar to my answer here just for your activity base class instead of application.

The longer, probably better way: refactor your code so that all activity injection (and more importantly graph creation) happens in one class, something like ActivityInjectHelper. In your test package, create another class named ActivityInjectHelper with the exact same package path that implements the same methods, except also plusses your test modules. Because test classes are loaded first, your application will execute with the testing ActivityInjectHelper. Again it's similar to my answer here just for a different class.

UPDATE:

I see you've posted more code and it's close to working, but no cigar. For both activities and applications, the test module needs to be snuck in before onCreate() runs. When dealing with activity object graphs, anytime before the test's getActivity() is fine. When dealing with applications, it's a bit harder because onCreate() has already been called by the time setUp() runs. Luckily, doing it in the test's constructor works - the application hasn't been created at that point. I briefly mention this in my first link.

这篇关于做ES preSSO功能测试,为Android的时候得到匕首注入mock对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆