简体   繁体   English

如何在Android中使用Dagger2 Dependency Injection和Robolectric进行测试?

[英]How to test with Dagger2 Dependency Injection & Robolectric in Android?

I recently implemented Dagger2 into an Android application for easy dependency injection but after doing so some of my tests have stopped working. 我最近将Dagger2应用到Android应用程序中以便于依赖注入,但在这样做后,我的一些测试已停止工作。

Now I am trying to understand how to adjust my tests to work with Dagger2? 现在我想了解如何调整我的测试以使用Dagger2? I am using Robolectric for running my tests. 我正在使用Robolectric来运行我的测试。

Here is how I use Dagger2, I have only recently learned it so this may be bad practice and not helping the tests so please do point out any improvements I can make. 以下是我如何使用Dagger2,我最近才学会它,所以这可能是不好的做法,并没有帮助测试,所以请指出我可以做的任何改进。

I have an AppModule which is as follows: 我有一个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;
    }
}

And what I call a NetworkModule that provides the objects for injection that is as follows: 我称之为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(){
    //...
}
}

And then the Component is like this: 然后组件是这样的:

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

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

For my tests I am using Robolectric, and I have a Test variant of my Application class as follows: 对于我的测试,我使用的是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;
    }
}

As you can see I am trying to make sure the mocked versions of my Dagger2 Modules are used, these are mocked as well with the mocked MyAppModule returning the TestMyApplication and the mocked NetworkModule returning mocked objects, I also have a mocked NetworkComponent which extends the real NetworkComponent. 正如您所看到的,我正在尝试确保使用我的Dagger2模块的模拟版本,这些也被模拟,模拟的MyAppModule返回TestMyApplication和模拟的NetworkModule返回模拟对象,我还有一个模拟的NetworkComponent,它扩展了真实NetworkComponent。

In the setup of a test I create the Activity using Robolectric like this: 在测试的设置中,我使用Robolectric创建Activity,如下所示:

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

controller.create(); //Create out Activity

This creates the Activity and starts the onCreate, and this is where the issue occurs, in the onCreate I have the following piece of code to inject the Activity into the component so it can use Dagger2 like this: 这会创建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();

The problem here is that when running the test I get a NullPointerException on the picasso variable, so I guess my Dagger2 setup has a missing link somewhere for the tests? 这里的问题是,当运行测试时,我在picasso变量上得到一个NullPointerException,所以我想我的Dagger2设置在测试的某个地方有一个缺失的链接?

EDIT: Adding TestNetworkModule 编辑:添加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);
    }

}

You don't need to add setters to your TestApplication and modules. 您无需在TestApplication和模块中添加setter。 You are using Dagger 2 so you should use it to inject dependencies in your test too: 您正在使用Dagger 2,因此您应该使用它来在测试中注入依赖项:

First in your MyApplication create a method to retrieve the ApplicationComponent. 首先在MyApplication中创建一个检索ApplicationComponent的方法。 This method will be overrided in the TestMyApplication class: 此方法将在TestMyApplication类中重写:

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

then create a TestNetworkComponent: 然后创建一个TestNetworkComponent:

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

In the TestNetworkModule return a mock 在TestNetworkModule中返回一个mock

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

In your TestMyApplication, build the TestNetworkComponent: 在TestMyApplication中,构建TestNetworkComponent:

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

then in your MainActivityTest run with the application tag and inject your dependency: 然后在MainActivityTest中运行应用程序标记并注入依赖项:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, application = TestMyApplication.class)
public class MainActivityTest {

    @Inject
    Picasso picasso;

    @Before
    public void setup() {
        ((TestMyApplication)RuntimeEnvironment.application).getOrCreateApplicationComponent().inject(this);
        Mockito.when(picasso.load(Matchers.anyString())).thenReturn(Mockito.mock(RequestCreator.class));
    }


    @Test
    public void test() {
        Robolectric.buildActivity(MainActivity.class).create();
    }

}

Your Picasso field has been injected with your Picasso mock now you can interact with it. 你的Picasso领域已经注入你的Picasso模拟现在你可以与之互动。

Just giving back mocks are not enough. 仅仅回馈嘲讽是不够的。 You need to instruct your mocks what they should return for different calls. 你需要告诉你的模拟他们应该为不同的电话返回什么。

I'm giving you an example for just the Picasso mock, but it should be similar for all. 我给你一个关于毕加索模拟的例子,但它应该与所有人相似。 I'm writing this on the Tube, so treat this as pseudo code. 我在管上写这个,所以把它当作伪代码。

Change your TestMyApplication so you can set the modules from outside something like this: 更改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;
    }
}

Now you can control your modules from the tests. 现在,您可以从测试中控制模块。


Next step make your mocks accesable. 下一步使您的模拟可访问。 Something like this: 像这样的东西:

@Module
public class TestNetworkModule {

    private Picasso picassoMock;

    ...

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

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

Now you can control all your mock. 现在你可以控制你所有的模拟了。


Now everything is set up for testing lets make one: 现在一切都设置为测试让我们做一个:

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

So now you can write your tests. 所以现在你可以编写测试了。 Because the setup is in the before you don't need to do this in every test. 因为设置在之前您不需要在每次测试中都这样做。

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

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