【问题标题】:How to test with Dagger2 Dependency Injection & Robolectric in Android?如何在 Android 中使用 Dagger2 依赖注入和 Robolectric 进行测试?
【发布时间】:2016-11-16 13:36:42
【问题描述】:

我最近在一个 Android 应用程序中实现了 Dagger2 以便于进行依赖注入,但在这样做之后,我的一些测试停止了工作。

现在我想了解如何调整我的测试以使用 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(){
    //...
}
}

然后Component是这样的:

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

}

【问题讨论】:

  • 您能在您的 TestNetworkModule 中添加什么吗?
  • 当然,现在就在那里
  • 你定义了当你的 picasso mock 被调用时应该发生什么?像这样:when(picassoMock.load(anyString())).thenReturn(something)
  • 不,我还没有实现,我需要在每个测试中都这样做吗?
  • 为什么在应用程序中使用单例? MyApplication.getInstance().getNetComponent().inject(this);您可以像这样访问您的 getNetComponent ((MyApplication) getApplication()).getNetComponent().inject(this);

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


【解决方案1】:

您无需将设置器添加到您的 TestApplication 和模块。您正在使用 Dagger 2,因此您也应该使用它在测试中注入依赖项:

首先在您的 MyApplication 中创建一个方法来检索 ApplicationComponent。此方法将在 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;
    }
}

然后创建一个TestNetworkComponent:

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

在 TestNetworkModule 中返回一个模拟

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

在您的 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;
    }
}

然后在您的 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();
    }

}

您的 Picasso 字段已被注入您的 Picasso 模拟,现在您可以与之交互了。

【讨论】:

  • 当我这样做时,当我在测试中使用 Mockito 时会出错 - 您不能在验证或存根之外使用参数匹配器。正确使用参数匹配器的示例:when(mock.get(anyInt())).thenReturn(null); doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject()); verify(mock).someMethod(contains("foo")) 此外,此错误可能会出现,因为您将参数匹配器与无法模拟的方法一起使用。以下方法不能被存根/验证:final/private/equals()/hashCode()。
  • 实际上退后一步,在遵循您的代码后,我无法从测试类中注入,我收到一个编译错误,指出:“错误:找不到合适的方法注入(MainActivityTest)我的匕首创建对象是这样的: DaggerTestMyApplication_TestNetworkComponent
  • 我已经完成了我的回答。请看看@DonalRafferty
  • “应用程序”已弃用
【解决方案2】:

仅仅回馈模拟是不够的。您需要指示您的模拟程序应该为不同的调用返回什么。

我只是给你一个毕加索模拟的例子,但它应该是相似的。 我在 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
    }
}

所以现在您可以编写测试了。因为设置是在之前你不需要在每个测试中都这样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-19
    • 2018-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多