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); } }
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With