简体   繁体   English

使用Robolectric和Dagger进行Android测试

[英]Android Testing with Robolectric and Dagger

I am trying to write an Android application using Dagger. 我正在尝试使用Dagger编写一个Android应用程序。 Trying to follow the TDD approach, I started writing a test for my First activity. 为了遵循TDD方法,我开始为First活动编写测试。 For writing tests I am using Robolectric and I am trying to make it work in different scenarios using Mockito. 为了编写测试,我正在使用Robolectric,并且正在尝试使用Mockito使它在不同的场景中工作。

Short story: 短篇故事:

I have an android activity which I want to test using robolectric. 我有一个Android活动,我想使用robolectric进行测试。 This activity has some of its dependencies provided through Dagger. 此活动具有通过Dagger提供的某些依赖项。 I managed to make this work by overriding the Application class and providing a mock of the utility class. 我设法通过重写Application类并提供实用程序类的模拟来使这项工作有效。 What I need now is to be able to change the behavior of the utility class(using Mockito) in the same unit test file to test different scenarios. 我现在需要的是能够在同一单元测试文件中更改实用程序类的行为(使用Mockito)以测试不同的场景。

Long story: 很长的故事:

Development and testing environment: Android Studio 0.8.6, gradle 0.12, dagger 1.2.2, robolectric 2.3 开发和测试环境:Android Studio 0.8.6,gradle 0.12,dagger 1.2.2,robolectric 2.3

Base Application class: 基础应用程序类:

public class MyApplication extends DaggerApplication
{

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

DaggerApplication class: 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;
}
}

Test Application: 测试应用:

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

Test Module class: 测试模块类别:

@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;
}
}

Test class: 测试类别:

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

}

If you need more information please do ask. 如果您需要更多信息,请询问。 To summarize: I would like to know how to use different test dagger modules in the same test class for different test scenarios. 总结:我想知道如何在相同的测试类中针对不同的测试场景使用不同的测试匕首模块。

Thank you. 谢谢。

Here's what you can do. 这是您可以做的。 Create two different modules in the test class. 在测试类中创建两个不同的modules One which provides Internet Connection as true and another as Internet Connection as False. 一个提供Internet连接为true,另一个提供Internet连接为False。 Once you have the two different module's setup inject them in the individual test class rather than the setUp of the Test Class. 一旦你有两个不同的模块设置在单独的测试类,而不是将它们注入setUp测试类的。 So: 所以:

@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;
}
}

The second module: 第二个模块:

@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;
}
}

And in you test class: 在您的测试课中:

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

Since you are injecting the modules in the test class itself, their scope is just local and you should be just fine. 由于您是在测试类本身中注入模块,因此它们的作用域只是局部的,您应该还不错。

Another way is you might just inject one of the modules in setUp . 另一种方法是,您可以只在setUp inject一个模块。 Use it across all the test cases. 在所有测试用例中使用它。 And just for the test that you need internet connection, inject the GeneralUtilsModuleWithInternetConnection in the test itself. 仅针对需要互联网连接的测试,将GeneralUtilsModuleWithInternetConnection注入测试本身。

Hope this helps. 希望这可以帮助。

Ok, first off, user2511882 I have tried your solution before posting the question but the thing is, if you look at the structure of TestMyApplication, where I inject the test module, you would see that your suggestion and my previous tries could not work. 好的,首先, user2511882我在发布问题之前已经尝试了您的解决方案,但是问题是,如果您查看TestMyApplication的结构(我在其中注入测试模块),您会发现您的建议和我之前的尝试都行不通。

After rethinking the whole problem I have found a solution along the lines of my initial tries and also a more useful solution (as far as I can see it). 在重新考虑了整个问题之后,我找到了与我最初的尝试类似的解决方案,并且找到了一个更有用的解决方案(据我所知)。 First off, I do not rely on the TestMyApplication class anymore. 首先,我不再依赖于TestMyApplication类。 Furthermore I had to do some changes to MyApplication class to make it more "test friendly" (without changing its functionality). 此外,我还必须对MyApplication类进行一些更改,以使其更“易于测试”(而不更改其功能)。 So MyApplication class looks like this: 因此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;
}
}

Now I can create the two test modules, one in which I set the behavior to return true when asking for an internet connection and one which will return false for the same query. 现在,我可以创建两个测试模块,其中一个我将行为设置为在请求互联网连接时返回true,而另一个将为同一查询返回false。

Now, in my test class I would have the following: 现在,在我的测试课中,我将具有以下内容:

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

}

This works fine and is along the lines of my initial plan of testing. 这工作正常,并且符合我最初的测试计划。 But I think there is a more elegant solution instead of creating a new test module for each situation. 但是我认为有一种更优雅的解决方案,而不是针对每种情况创建新的测试模块。 The modified test module looks like this: 修改后的测试模块如下所示:

@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;
    }
}

Using this, the Test class looks like this: 使用此方法,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)....
}

Thank you all for your help and I really hope that this solution will help others achieve better testing on Android. 谢谢大家的帮助,我真的希望这个解决方案能够帮助其他人在Android上实现更好的测试。

Seems like you're looking for module override (like Roboguice does). 好像您在寻找模块替代(就像Roboguice一样)。 I couldn't find any, but in my tests, I've been using something like this: 我找不到任何东西,但是在测试中,我一直在使用这样的东西:

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

If MyObject is used in an Activity , I can also test it: 如果在Activity使用MyObject ,我也可以对其进行测试:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM