【问题标题】:Android Testing with Robolectric and Dagger使用 Robolectric 和 Dagger 进行 Android 测试
【发布时间】:2014-08-14 22:03:52
【问题描述】:

我正在尝试使用 Dagger 编写一个 Android 应用程序。为了遵循 TDD 方法,我开始为我的第一个活动编写测试。对于编写测试,我使用的是 Robolectric,并且我正在尝试使用 Mockito 使其在不同的场景中工作。

短篇小说:

我有一个想要使用 robolectric 测试的 android 活动。这个活动有一些通过 Dagger 提供的依赖项。我设法通过重写 Application 类并提供实用程序类的模拟来完成这项工作。我现在需要的是能够在同一个单元测试文件中更改实用程序类的行为(使用 Mockito)来测试不同的场景。

长篇大论:

开发测试环境:Android Studio 0.8.6、gradle 0.12、dagger 1.2.2、robolectric 2.3

基础应用类:

public class MyApplication extends DaggerApplication
{

@Override
protected List<Object> getAppModules() {
    List<Object> modules = new ArrayList<Object>();
    modules.add(new AppModule(this));
    return modules;
}
}

DaggerApplication 类:

public abstract class DaggerApplication extends Application {

private ObjectGraph mObjectGraph;

@Override
public void onCreate() {
    super.onCreate();

    AndroidAppModule sharedAppModule = new AndroidAppModule(this);

    List<Object> modules = new ArrayList<Object>();
    modules.add(sharedAppModule);
    modules.addAll(getAppModules());

    mObjectGraph = ObjectGraph.create(modules.toArray());
}

protected abstract List<Object> getAppModules();

@Override
public void inject(Object object) {
    mObjectGraph.inject(object);
}

@Override
public ObjectGraph getObjectGraph() {
    return mObjectGraph;
}
}

测试应用:

public class TestMyApplication extends MyApplication{

@Override
protected List<Object> getAppModules() {
    List<Object> modules = super.getAppModules();
    modules.add(new GeneralUtilsModuleNoInternetConnection());
    return modules;
}    

public static <T> void injectMocks(T object) {
    CursuriDeSchimbApplication app = (TestCursuriDeSchimbApplication) Robolectric.application;
    app.inject(object);
}
}

AppModule 类:

@Module(
    injects = {
            SplashScreenActivity.class
    },
    includes = AndroidAppModule.class
)
public class AppModule {

private Context app;

public AppModule()
{

}

public AppModule(Context app) {
    this.app = app;
}

@Provides
@Singleton
GeneralUtils provideGeneralUtils() {
    return new GeneralUtils();
}
}

测试模块类:

@Module(
    includes = AppModule.class,
    injects = {SplashScreenActivityTest.class,
            SplashScreenActivity.class},
    overrides = true
)
public class GeneralUtilsModuleNoInternetConnection
{
public GeneralUtilsModuleNoInternetConnection() {
}

@Provides
@Singleton
GeneralUtils provideGeneralUtils() {

    GeneralUtils mockGeneralUtils = Mockito.mock(GeneralUtils.class);

    when(mockGeneralUtils.isInternetConnection()).thenReturn(false);

    return mockGeneralUtils;
}
}

测试类:

@RunWith(RobolectricTestRunner.class)
public class SplashScreenActivityTest
{
SplashScreenActivity activity;

@Before
public void setUp()
{
    activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
}


@Test
public void testOnCreate_whenNoInternetConnection()
{
   <!-- Here I want GeneralUtils to return false when asking for internet connection -->
}
@Test
public void testOnCreate_whenThereIsInternetConnection()
{
   <!-- Here I want GeneralUtils to return true when asking for internet connection -->
}

}

如果您需要更多信息,请务必询问。 总结一下:我想知道如何在同一个测试类中针对不同的测试场景使用不同的测试匕首模块。

谢谢。

【问题讨论】:

  • 测试类中正在初始化的模块在哪里?
  • 它在 TestMyApplication 类中初始化,该类由 robolectric 测试而不是 MyApplication 使用。具体代码为getAppModules()方法中的modules.add(new GeneralUtilsModuleNoInternetConnection());

标签: android unit-testing robolectric dagger


【解决方案1】:

这是你可以做的。在测试类中创建两个不同的modules。一个提供 Internet 连接为真,另一个提供 Internet 连接为假。一旦你设置了两个不同的模块,就将它们注入到单独的测试类中,而不是测试类的setUp。所以:

@Module(
    includes = AppModule.class,
    injects = {SplashScreenActivityTest.class,
            SplashScreenActivity.class},
    overrides = true
)
public class GeneralUtilsModuleNoInternetConnection
{
public GeneralUtilsModuleNoInternetConnection() {
}

@Provides
@Singleton
GeneralUtils provideGeneralUtils() {

    GeneralUtils mockGeneralUtils = Mockito.mock(GeneralUtils.class);

    when(mockGeneralUtils.isInternetConnection()).thenReturn(false);

    return mockGeneralUtils;
}
}

第二个模块:

@Module(
    includes = AppModule.class,
    injects = {SplashScreenActivityTest.class,
            SplashScreenActivity.class},
    overrides = true
)
public class GeneralUtilsModuleWithInternetConnection
{
public GeneralUtilsModuleNoInternetConnection() {
}

@Provides
@Singleton
GeneralUtils provideGeneralUtils() {

    GeneralUtils mockGeneralUtils = Mockito.mock(GeneralUtils.class);

    when(mockGeneralUtils.isInternetConnection()).thenReturn(true);

    return mockGeneralUtils;
}
}

在你的测试类中:

    @Test
    public void testOnCreate_whenNoInternetConnection()
    {
       <!-- Here You want to inject the GeneralUtilsModuleNoInternetConnection module and test it out-->
    }
    @Test
    public void testOnCreate_whenThereIsInternetConnection()
    {
       <!-- Here You want to inject the GeneralUtilsModuleWithInternetConnection module and test it out -->
    }

由于您在测试类本身中注入模块,因此它们的范围只是本地的,您应该没问题。

另一种方法是您可能只是inject setUp 中的一个模块。在所有测试用例中使用它。并且仅针对需要 Internet 连接的测试,在测试本身中注入 GeneralUtilsModuleWithInternetConnection

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    好的,首先,user2511882 在发布问题之前我已经尝试过您的解决方案,但问题是,如果您查看我注入测试模块的 TestMyApplication 的结构,您会看到您的建议和我之前的建议尝试失败。

    在重新考虑整个问题后,我找到了一个与我最初尝试类似的解决方案,并且还找到了一个更有用的解决方案(据我所知)。首先,我不再依赖 TestMyApplication 类。此外,我必须对 MyApplication 类进行一些更改,以使其更加“测试友好”(不更改其功能)。所以 MyApplication 类看起来像这样:

    public class MyApplication extends DaggerApplication
    {
       private List<Object> modules;
       public MyApplication() {
           modules = new ArrayList<Object>();
           modules.add(new AppModule(this));
       }
    
    @Override
    protected List<Object> getAppModules() {
        return modules;
    }
    }
    

    现在我可以创建两个测试模块,一个在其中我将行为设置为在请求互联网连接时返回 true,另一个将在同一查询中返回 false。

    现在,在我的测试课中,我将拥有以下内容:

    @RunWith(RobolectricTestRunner.class)
    public class SplashScreenActivityTest
    {
        SplashScreenActivity activity;
    
        public void setUpNoInternet()
        {
    // Now I can add the new test module to the application modules to override the real one in the application onCreate() method
            ((MyApplication)Robolectric.application).getAppModules().add(new GeneralUtilsModuleNoInternetConnection());
            activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
        }
        public void setUpWithInternet()
        {
            ((MyApplication)Robolectric.application).getAppModules().add(new GeneralUtilsModuleWithInternetConnection());
            activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
        }
    
    
        @Test
        public void testOnCreate_whenNoInternetConnection()
        {
            setUpNoInternet();
           <!-- Assertions -->
        }
        @Test
        public void testOnCreate_whenThereIsInternetConnection()
        {
            setUpWithInternet();
           <!-- Assertions -->
        }
    
    }
    

    这很好用,并且符合我最初的测试计划。但我认为有一个更优雅的解决方案,而不是为每种情况创建一个新的测试模块。修改后的测试模块如下所示:

    @Module(
        includes = AppModule.class,
        injects = {SplashScreenActivityTest.class,
                SplashScreenActivity.class},
        overrides = true
    )
    public class GeneralUtilsModuleTest
    {
        private  GeneralUtils mockGeneralUtils;
    
        public GeneralUtilsModuleTest() {
            mockGeneralUtils = Mockito.mock(GeneralUtils.class);
        }
    
        @Provides
        @Singleton
        GeneralUtils provideGeneralUtils() {
    
            return mockGeneralUtils;
        }
    
        public GeneralUtils getGeneralUtils()
        {
            return mockGeneralUtils;
        }
    
        public void setGeneralUtils(final GeneralUtils generalUtils)
        {
            this.mockGeneralUtils = generalUtils;
        }
    }
    

    使用这个,Test 类看起来像这样:

        @RunWith(RobolectricTestRunner.class)
    public class SplashScreenActivityTest
    {
        SplashScreenActivity activity;
    
        private GeneralUtilsModuleTest testModule;
        private GeneralUtils generalUtils;
    
        @Before
        public void setUp()
        {
            testModule = new GeneralUtilsModuleTest();
            generalUtils = Mockito.mock(GeneralUtils.class);
        }
    
        public void setUpNoInternet()
        {
            when(generalUtils.isInternetConnection()).thenReturn(false);
            testModule.setGeneralUtils(generalUtils);
            ((MyApplication)Robolectric.application).getAppModules().add(testModule);
            activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
        }
        public void setUpWithInternet()
        {
            when(generalUtils.isInternetConnection()).thenReturn(true);
            testModule.setGeneralUtils(generalUtils);
            (MyApplication)Robolectric.application).getAppModules().add(testModule);
            activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
        }
        .....(Tests)....
    }
    

    感谢大家的帮助,我真的希望这个解决方案能帮助其他人在 Android 上实现更好的测试。

    【讨论】:

    • 不错的解决方案...实用且优雅。唯一需要注意的是确保在添加 testModule 之后 在 TestApplication 中重新创建 ObjectGraph。这是必要的,因为 Robolectric 将在运行测试之前调用TestApplication.onCreate(),因此应用程序 ObjectGraph 已经被巩固。
    【解决方案3】:

    似乎您正在寻找模块覆盖(就像 Roboguice 一样)。我找不到任何东西,但在我的测试中,我一直在使用这样的东西:

    MyObjectTest.java
    
    @Test
    public void testMyObject() {
        ObjectGraph objectGraph = ObjectGraph.create(new TestModule());
        MyObject object = objectGraph.get(MyObject.class);
        assertNotNull(object);
        assertEquals("Received message from MyObjectTestImpl", object.getMessage());
    }
    
    TestModule.java
    
    public class TestModule {
        @Provides
        public Library provideMyObject() {
            return new MyObjectTestImpl();
        }
    }
    

    如果MyObject用在Activity中,我也可以测试一下:

    @RunWith(RoboGradleTestRunner.class)
    public class RoboTest {
        @Test
        public void testTextView() {
            MainActivity activity = (MainActivity) Robolectric.buildActivity(MainActivity.class).create().get();
    
            assertEquals("Received message from MyObjectTestImpl", activity.getMyObject().getMessage());
        }
    }
    

    【讨论】:

    • 感谢您的回答,但正如我在描述中所写,我已经使用了一个覆盖应用程序模块的测试模块。问题是我试图为同一个类(GeneralUtils)使用 2 个不同的模拟对象,它们应该在 2 个不同的场景中为函数返回不同的值。
    猜你喜欢
    • 1970-01-01
    • 2016-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-07
    • 1970-01-01
    • 2016-06-15
    相关资源
    最近更新 更多