如何在 Android 中使用 Dagger2 依赖注入和 Robolectric 进行测试?
How to test with Dagger2 Dependency Injection & Robolectric in Android?
我最近将 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,我的应用程序 class 有一个测试变体,如下所示:
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 设置在测试的某个地方缺少 link?
编辑:添加 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);
}
}
仅仅回馈模拟是不够的。您需要指示您的模拟程序 return 应对不同的调用。
我给你举的只是 Picasso mock 的例子,但它应该对所有人都相似。
我在地铁上写这个,所以把它当作伪代码。
更改您的 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
}
}
现在您可以编写测试了。因为设置在之前,所以你不需要在每次测试中都这样做。
您不需要向 TestApplication 和模块添加设置器。您正在使用 Dagger 2,因此您也应该使用它在测试中注入依赖项:
首先在您的 MyApplication 中创建一个方法来检索 ApplicationComponent。此方法将在 TestMyApplication class:
中被覆盖
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 return 一个 mock
@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 模型,现在您可以与之交互了。
我最近将 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,我的应用程序 class 有一个测试变体,如下所示:
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 设置在测试的某个地方缺少 link?
编辑:添加 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);
}
}
仅仅回馈模拟是不够的。您需要指示您的模拟程序 return 应对不同的调用。
我给你举的只是 Picasso mock 的例子,但它应该对所有人都相似。 我在地铁上写这个,所以把它当作伪代码。
更改您的 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
}
}
现在您可以编写测试了。因为设置在之前,所以你不需要在每次测试中都这样做。
您不需要向 TestApplication 和模块添加设置器。您正在使用 Dagger 2,因此您也应该使用它在测试中注入依赖项:
首先在您的 MyApplication 中创建一个方法来检索 ApplicationComponent。此方法将在 TestMyApplication class:
中被覆盖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 return 一个 mock
@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 模型,现在您可以与之交互了。