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 that Service

    .
  • 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 using context.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?

+3


source to share


1 answer


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

.

+1


source







All Articles