A unit test of a running Service with multiple fields entered?
I am new to dagger.
TL; DR:
- If Android
Service
has any fields nested in it using Dagger, then in order to actually perform the injection, I need to have an instance of thatService
. - In Robolectric's tests, this is consistent
MyService service = Robolectric.buildService(MyService.class).get()
. And then,objectGraph.inject(service);
- However, the rest of the code that actually runs
MyService
is still usingcontext.startService(context, MyService.class);
.
Q: What is the idiomatic approach in a dagger to solve this problem?
Let's say I have the Service
following:
public class MyService {
@Inject Parser parser;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String data = intent.getStringExtra("data_to_be_parsed");
parser.parse(data);
}
}
Elsewhere in my code, I have an ApiClient class that does this:
public class ApiClient{
public static void parseInBackground(Context context, String data){
//This service does not have its fields injected
context.startService(new Intent(context, MyService.class).putExtra("data_to_be_parsed", data));
}
}
This method parseInBackground
is called from the Activity in response to user interaction.
I am now following TDD and hence I have not yet written an Application Module to do this. Here's the test module:
@Module(injects = MyService.class)
public class TestModule {
@Provides @Singleton Parser provideParser(){
return new MockParser();
}
}
And finally a test case:
@RunWith(Robolectric.class)
public class ApiTest {
@Test
public void parseInBackground_ParsesCorrectly(){
//This service has its fields injected
MyService service = Robolectric.buildService(MyService.class).get();
ObjectGraph.create(new TestModule()).inject(service);
ApiClient.parseInBackground(Robolectric.application, "<user><name>droid</name></user>");
//Asserts here
}
}
As you can see, in the test I am retrieving the service instance and injecting into it MockParser
. However, the class ApiClient
starts the service directly with the Intent. I have no way to inject.
I know that I can MyService
do the injection on my own:
public void onCreate(){
ObjectGraph.create(new TestModule()).inject(this);
}
But then I am hard-coding TestModule
here.
Is there an existing idiom in the dagger for setting up dependencies for situations like this?
source to share
This is the wrong way to hard-code your modules in either tests or services. The best way to do the creation is through your custom object Application
, which in turn will contain a singleton object ObjectGraph
. For example:
// in MyService class
@Override public void onCreate() {
super.onCreate();
MyApp.from(context).inject(this);
}
// in MyApp class
public static MyApp from(Context context) {
return (MyApp) context.getApplicationContext();
}
//...
private ObjectGraph objectGraph;
@Override public void onCreate() {
// Perform Injection
objectGraph = ObjectGraph.create(getModules());
objectGraph.inject(this);
}
public void inject(Object object) {
objectGraph.inject(object);
}
protected Object[] getModules() {
// return concrete modules based on build type or any other conditions.
}
Alternatively, you can refactor the last method into a separate class and make different implementations for different flavors or assembly types. Also you can set overrides=true
in annotations TestModule
.
source to share