使用Robolectric和Dagger进行Android测试

时间:2014-08-14 22:03:52

标签: android unit-testing robolectric dagger

我正在尝试使用Dagger编写Android应用程序。为了遵循TDD方法,我开始为我的First活动编写测试。为了编写测试我正在使用Robolectric,我正在尝试使用Mockito在不同的场景中使用它。

短篇小说:

我有一个Android活动,我想用robolectric测试。此活动通过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 -->
}

}

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

谢谢。

3 个答案:

答案 0 :(得分:2)

这是你可以做的。在测试类中创建两个不同的modules。一个提供Internet连接为true,另一个提供Internet连接为False。一旦你有两个不同的模块设置将它们注入单独的测试类而不是测试类的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中只有GeneralUtilsModuleWithInternetConnection个模块。在所有测试用例中使用它。只是为了测试你需要互联网连接,在测试中注入{{1}}。

希望这有帮助。

答案 1 :(得分:1)

好的,首先,user2511882我在发布问题之前尝试了你的解决方案,但问题是,如果你看看TestMyApplication的结构,我注入了测试模块,你会看到你的建议和我之前的尝试无法奏效。

在重新思考整个问题之后,我找到了一个与我最初的尝试一致的解决方案,也是一个更有用的解决方案(据我所知)。首先,我不再依赖TestMyApplication类了。此外,我必须对MyApplication类进行一些更改,以使其更加“测试友好”#34; (不改变其功能)。所以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上实现更好的测试。

答案 2 :(得分:0)

好像你正在寻找模块覆盖(就像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());
    }
}