Tests for Android with Dagger 2

I have an Android app that uses Dagger 2 to inject dependencies. I also use the latest gradle build tools, which allow the build option for unit testing and one for benchmarks. I am using java.util.Random in my application and I want to mock it for testing. The classes I'm testing don't use any Android elements, so they are just regular Java classes.

In my main code, I define Component in a class that extends the Application class, but in unit tests I do not use Application . I tried to define the Module and Component test, but the Dagger will not generate Component . I also tried using Component , which I defined in my application, and swapping Module when creating it, but the Component application does not have inject methods for my test classes. How can I provide a mock implementation of Random for testing?

Here is a 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; } } 

Testing module:

 @Module public class PipeGameTestModule { @Provides @Singleton Random provideRandom() { return mock(Random.class); } } 
+46
java android unit-testing dagger-2
May 01 '15 at 14:55
source share
5 answers

This is currently not possible with dagger 2 (starting with version 2.0) without any workarounds. You can read about it here .

Learn more about possible workarounds:

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

  • Creating Test Dependencies Using Dagger2

+25
May 05 '15 at
source share

You hit a nail on the head, saying:

The component has no input 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 for all this to be done in the test, we can use Robolectric.

1) Create a test version of the 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) In your source application class there must be 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 the constructor:

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

4) Now, in your setup () junit method, install this test module in a test application:

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

Now you can configure your test module as you like.

+6
Apr 04 '16 at 0:45
source share

In my opinion, you can approach this problem by looking at it from a different angle. You can easily unit test your class, regardless of the dagger for the test building class with its mocked dependencies introduced into it.

I want to say that in the test setup you can:

  • Define class dependencies
  • Build a class under the test manually using mocked dependencies

We do not need to verify that dependencies are entered correctly, as Dagger checks the dependency graph is correct at compile time. Thus, any errors will be reported as a result of a compilation failure. This is why it should be acceptable to manually create the test class in the installation method.

Sample code in which a dependency is entered using the constructor in the tested class:

 public class BoardModelTest { private BoardModel boardModel; private Random random; @Before public void setUp() { random = mock(Random.class); boardModel = new BoardModel(random); } @Test ... } public class BoardModel { private Random random; @Inject public BoardModel(Random random) { this.random = random; } ... } 

Sample code in which a dependency is entered using a field in the tested class (in case BoardModel is created by the framework):

 public class BoardModelTest { private BoardModel boardModel; private Random random; @Before public void setUp() { random = mock(Random.class); boardModel = new BoardModel(); boardModel.random = random; } @Test ... } public class BoardModel { @Inject Random random; public BoardModel() {} ... } 
+2
May 05 '15 at 17:37
source share

If you are using dagger2 with Android, you can use App Affrabs to provide mocking resources.

See here a demonstration of tastes during mock testing (without a dagger): https://www.youtube.com/watch?v=vdasFFfXKOY

There is an example in this codebase: https://github.com/googlecodelabs/android-testing

In /src/prod/com/yourcompany/Component.java you provide your production components.

In /src/mock/com/yourcompany/Component.java you provide your mocking components.

This allows you to build your application with or without ridicule. It also allows parallel development (support for one team, frontend application for another team), you can make fun of it until api methods are available.

What my gradle commands (its Makefile) look like:

 install_mock: ./gradlew installMockDebug install: ./gradlew installProdDebug test_unit: ./gradlew testMockDebugUnitTest test_integration_mock: ./gradlew connectedMockDebugAndroidTest test_integration_prod: ./gradlew connectedProdDebugAndroidTest 
+1
Feb 26 '16 at 2:02
source share

Actually, I had the same problem and I found a very simple solution. This is not the best possible solution, I think, but it will solve your problem.

Create a similar class in your application module:

 public class ActivityTest<T extends ViewModelBase> { @Inject public T vm; } 

Then in the AppComponent app add:

 void inject(ActivityTest<LoginFragmentVM> activityTest); 

You can then enter this into your test class.

  public class HelloWorldEspressoTest extends ActivityTest<LoginFragmentVM> { @Rule public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class); @Test public void listGoesOverTheFold() throws InterruptedException { App.getComponent().inject(this); vm.email.set("1234"); closeSoftKeyboard(); } } 
0
Jul 30 '17 at 15:15
source share



All Articles