如何使用Dagger2依赖注入和测试进行测试Android中的Robolectric?

时间:2016-07-13 19:54:50

标签: java android unit-testing robolectric dagger-2

我最近将Dagger2应用到Android应用程序中以便于依赖注入,但在执行此操作后,我的一些测试已停止工作。

现在我想了解如何调整我的测试以使用Dagger2?我正在使用Robolectric来运行我的测试。

以下是我使用Dagger2的方法,我最近才学会了,所以这可能是不好的做法而且没有帮助测试,所以请指出我可以做的任何改进。

我有一个AppModule,如下所示:

@Module
public class MyAppModule {

    //Application reference
    Application mApplication;

    //Set the application value
    public MyAppModule(Application application) {
        mApplication = application;
    }

    //Provide a singleton for injection
    @Provides
    @Singleton
    Application providesApplication() {
        return mApplication;
    }
}

我所谓的NetworkModule提供了注入对象,如下所示:

@Module
public class NetworkModule {

private Context mContext;

//Constructor that takes in the required context and shared preferences objects
public NetworkModule(Context context){
    mContext = context;
}

@Provides
@Singleton
SharedPreferences provideSharedPreferences(){
    //...
}

@Provides @Singleton
OkHttpClient provideOKHttpClient(){
    //...
}

@Provides @Singleton
Picasso providePicasso(){
    //...
}

@Provides @Singleton
Gson provideGson(){
    //...
}
}

然后组件是这样的:

Singleton
@Component(modules={MyAppModule.class, NetworkModule.class})
public interface NetworkComponent {

    //Activities that the providers can be injected into
    void inject(MainActivity activity);
    //...
}

对于我的测试我使用的是Robolectric,我的Application类的Test变量如下:

public class TestMyApplication extends TestApplication {

    private static TestMyApplication sInstance;
    private NetworkComponent mNetworkComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
        mNetworkComponent = DaggerTestMyApplication_TestNetworkComponent.builder()
                .testMyAppModule(new TestMyAppModule(this))
                .testNetworkModule(new TestNetworkModule(this)).build();
    }

    public static MyApplication getInstance() {
        return sInstance;
    }

    @Override public NetworkComponent getNetComponent() {
        return mNetworkComponent;
    }
}

正如您所看到的,我正在尝试确保使用我的Dagger2模块的模拟版本,这些也被模拟,模拟的MyAppModule返回TestMyApplication和模拟的NetworkModule返回模拟对象,我也有一个模拟的NetworkComponent扩展了真正的NetworkComponent。

在测试设置中,我使用Robolectric创建Activity,如下所示:

//Build activity using Robolectric
ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);
activity = controller.get();

controller.create(); //Create out Activity

这会创建Activity并启动onCreate,这就是问题发生的地方,在onCreate中我有以下代码片段将Activity注入组件,因此它可以像这样使用Dagger2:

@Inject Picasso picasso; //Injected at top of Activity

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
MyApplication.getInstance().getNetComponent().inject(this); 

picasso.load(url).fetch();

这里的问题是,在运行测试时,我在picasso变量上得到一个NullPointerException,所以我猜我的Dagger2设置在测试的某个地方缺少一个链接?

编辑:添加TestNetworkModule

@Module
public class TestNetworkModule {

    public TestNetworkModule(Context context){

    }

    @Provides
    @Singleton
    SharedPreferences provideSharedPreferences(){
        return Mockito.mock(SharedPreferences.class);
    }


    @Provides @Singleton
    Gson provideGson(){
        return Mockito.mock(Gson.class);
    }

    @Provides @Singleton
    OkHttpClient provideOKHttpClient(){
        return Mockito.mock(OkHttpClient.class);
    }

    @Provides @Singleton
    Picasso providePicasso(){
        return  Mockito.mock(Picasso.class);
    }

}

2 个答案:

答案 0 :(得分:10)

您无需在TestApplication和模块中添加setter。您正在使用Dagger 2,因此您应该使用它来在测试中注入依赖项:

首先在MyApplication中创建一个检索ApplicationComponent的方法。此方法将在TestMyApplication类中重写:

from py2neo import Graph
graph = Graph(password="excalibur")
graph.data("MATCH (a:Person) RETURN a.name, a.born LIMIT 4")
[{'a.born': 1964, 'a.name': 'Keanu Reeves'},
 {'a.born': 1967, 'a.name': 'Carrie-Anne Moss'},
 {'a.born': 1961, 'a.name': 'Laurence Fishburne'},
 {'a.born': 1960, 'a.name': 'Hugo Weaving'}]

然后创建一个TestNetworkComponent:

public class MyApplication extends Application {

    private ApplicationComponent mApplicationComponent;

    public ApplicationComponent getOrCreateApplicationComponent() {
        if (mApplicationComponent == null) {
            mApplicationComponent = DaggerApplicationComponent.builder()
                    .myAppModule(new MyAppModule(this))
                    .networkModule(new NetworkModule())
                    .build();
        }
        return mApplicationComponent;
    }
}

在TestNetworkModule中返回一个模拟

@Singleton
@Component(modules = {MyAppModule.class, TestNetworkModule.class})
public interface TestApplicationComponent extends ApplicationComponent {
    void inject(MainActivityTest mainActivityTest);
}

在TestMyApplication中,构建TestNetworkComponent:

@Provides
@Singleton
Picasso providePicasso(){
    return Mockito.mock(Picasso.class);
}

然后在MainActivityTest中运行应用程序标记并注入依赖项:

public class TestMyApplication extends MyApplication {

    private TestApplicationComponent testApplicationComponent;

    @Override
    public TestApplicationComponent getOrCreateApplicationComponent() {
        if (testApplicationComponent == null) {
            testApplicationComponent = DaggerTestApplicationComponent
                    .builder()
                    .myAppModule(new MyAppModule(this))
                    .testNetworkModule(new TestNetworkModule())
                    .build();
        }
        return testApplicationComponent;
    }
}

您的Picasso字段已经注入您的Picasso模拟,现在您可以与它进行交互。

答案 1 :(得分:1)

仅仅回馈嘲讽是不够的。你需要告诉你的模拟他们应该为不同的电话返回什么。

我给你一个关于Picasso模拟的例子,但它应该与所有人相似。 我在Tube上写这个,所以把它当作伪代码。

更改您的TestMyApplication,以便您可以从外部设置模块:

public class TestMyApplication extends TestApplication {

    private static TestMyApplication sInstance;
    private NetworkComponent mNetworkComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }

    public void setModules(MyAppModule applicationModule, NetworkModule networkModule) {
        this.applicationModule = applicationModule;
        this.mNetworkComponent = DaggerApplicationComponent.builder()
                .applicationModule(applicationModule)
                .domainModule(networkModule)
                .build();
    }

    public static MyApplication getInstance() {
        return sInstance;
    }

    @Override public NetworkComponent getNetComponent() {
        return mNetworkComponent;
    }
}

现在,您可以从测试中控制模块。

下一步让你的模拟可以访问。像这样:

@Module
public class TestNetworkModule {

    private Picasso picassoMock;

    ...

    @Provides @Singleton
    Picasso providePicasso(){
        return picassoMock;
    }

    public void setPicasso(Picasso picasso){
        this.picasso = picasso;
    }
}

现在你可以控制你的所有模拟。

现在一切都已设置好进行测试,让我们做一个:

@RunWith(RobolectricGradleTestRunner.class)
public class PicassoTest {

    @Mock Picasso picasso;
    @Mock RequestCreator requestCreator;

    @Before
    public void before(){
        initMocks(this);

        when(picassoMock.load(anyString())).thenReturn(requestCreator);

        TestApplication app = (TestApplication) RuntimeEnvironment.application;

        TestNetworkModule networkModule = new TestNetworkModule(app);
        networkModule.setPicasso(picasso);

        app.setModules(new TestMyAppModule(this), networkModule);
        //Build activity using Robolectric
        ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);
        activity = controller.get();
        activity.create();
    }

    @Test
    public void test(){
        //the test
    }

    @Test
    public void test2(){
        //another test
    }
}

所以现在你可以编写测试了。因为设置是在您不需要在每次测试中执行此操作之前。