Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Unit Tests with Dagger 2

I have an Android app that uses Dagger 2 for dependency injection. I am also using the latest gradle build tools that allow a build variant for unit testing and one for instrumentation tests. I am using java.util.Random in my app, and I want to mock this for testing. The classes I'm testing don't use any Android stuff, so they're just regular java classes.

In my main code I define a Component in a class that extends the Application class, but in the unit tests I'm not using an Application. I tried defining a test Module and Component, but Dagger won't generate the Component. I have also tried using the Component that I defined in my application and swapping the Module when I build it, but the application's Component doesn't have inject methods for my test classes. How can I provide a mock implementation of Random for testing?

Here's some sample code:

Application:

public class PipeGameApplication extends Application {      private PipeGame pipeGame;      @Singleton     @Component(modules = PipeGameModule.class)     public interface PipeGame {         void inject(BoardFragment boardFragment);         void inject(ConveyorFragment conveyorFragment);     }      @Override     public void onCreate() {         super.onCreate();         pipeGame = DaggerPipeGameApplication_PipeGame.create();     }      public PipeGame component() {         return pipeGame;     } } 

Module:

@Module public class PipeGameModule {      @Provides     @Singleton     Random provideRandom() {         return new Random();     } } 

Base class for tests:

public class BaseModelTest {      PipeGameTest pipeGameTest;      @Singleton     @Component(modules = PipeGameTestModule.class)     public interface PipeGameTest {         void inject(BoardModelTest boardModelTest);         void inject(ConveyorModelTest conveyorModelTest);     }      @Before     public void setUp() {         pipeGameTest = DaggerBaseModelTest_PipeGameTest.create(); // Doesn't work     }      public PipeGameTest component() {         return pipeGameTest;     } } 

or:

public class BaseModelTest {      PipeGameApplication.PipeGame pipeGameTest;      // This works if I make the test module extend     // the prod module, but it can't inject my test classes     @Before     public void setUp() {         pipeGameTest = DaggerPipeGameApplication_PipeGame.builder().pipeGameModule(new PipeGameModuleTest()).build();     }      public PipeGameApplication.PipeGame component() {         return pipeGameTest;     } } 

Test Module:

@Module public class PipeGameTestModule {      @Provides     @Singleton     Random provideRandom() {         return mock(Random.class);     } } 
like image 589
Pikaling Avatar asked May 01 '15 14:05

Pikaling


Video Answer


2 Answers

This is currently impossible with Dagger 2 (as of v2.0.0) without some workarounds. You can read about it here.

More about possible workarounds:

  • How do you override a module/dependency in a unit test with Dagger 2.0?

  • Creating test dependencies when using Dagger2

like image 191
tomrozb Avatar answered Sep 21 '22 15:09

tomrozb


You have hit the nail on the head by saying:

application's Component doesn't have inject methods for my test classes

So, to get around this problem we can make a test version of your Application class. Then we can have a test version of your module. And to make it all run in a test, we can use Robolectric.

1) Create the test version of your Application class

public class TestPipeGameApp extends PipeGameApp {     private PipeGameModule pipeGameModule;      @Override protected PipeGameModule getPipeGameModule() {         if (pipeGameModule == null) {             return super.pipeGameModule();         }         return pipeGameModule;     }      public void setPipeGameModule(PipeGameModule pipeGameModule) {         this.pipeGameModule = pipeGameModule;         initComponent();     }} 

2) Your original Application class needs to have initComponent() and pipeGameModule() methods

public class PipeGameApp extends Application {     protected void initComponent() {         DaggerPipeGameComponent.builder()             .pipeGameModule(getPipeGameModule())             .build();     }      protected PipeGameModule pipeGameModule() {         return new PipeGameModule(this);     }} 

3) Your PipeGameTestModule should extend the production module with a constructor:

public class PipeGameTestModule extends PipeGameModule {     public PipeGameTestModule(Application app) {         super(app);     }} 

4) Now, in your junit test's setup() method, set this test module on your test app:

@Before public void setup() {     TestPipeGameApp app = (TestPipeGameApp) RuntimeEnvironment.application;     PipeGameTestModule module = new PipeGameTestModule(app);     app.setPipeGameModule(module); } 

Now you can customize your test module how you originally wanted.

like image 30
IgorGanapolsky Avatar answered Sep 22 '22 15:09

IgorGanapolsky