簡體   English   中英

使用Robolectric和Dagger進行Android測試

[英]Android Testing with Robolectric and 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 -->
}

}

如果您需要更多信息,請詢問。 總結:我想知道如何在相同的測試類中針對不同的測試場景使用不同的測試匕首模塊。

謝謝。

這是您可以做的。 在測試類中創建兩個不同的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 -->
    }

由於您是在測試類本身中注入模塊,因此它們的作用域只是局部的,您應該還不錯。

另一種方法是,您可以只在setUp inject一個模塊。 在所有測試用例中使用它。 僅針對需要互聯網連接的測試,將GeneralUtilsModuleWithInternetConnection注入測試本身。

希望這可以幫助。

好的,首先, 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上實現更好的測試。

好像您在尋找模塊替代(就像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();
    }
}

如果在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