Overriding debug module in tests - android

I have a Gradle app with a project structure similar to Jake Wharton's u2020:
/src
/androidTest
/debug
/main
/release
In my application class I build the Dagger graph and inject it:
MyApplication extends Application {
...
public void buildObjectGraphAndInject() {
Object[] modules = Modules.list(this);
mApplicationGraph = ObjectGraph.create(modules);
mApplicationGraph.inject(this);
}
...
}
Inside the debug sourceset, I define Modules.list() as:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app),
new DebugAppModule()
};
}
private Modules() {
// No instances.
}
}
Inside the release sourceset, I define the same thing minus the DebugAppModule:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app)
};
}
private Modules() {
// No instances.
}
}
Deeper down my dependency graph, I create a MockRestAdapter that I can use when running the debug version:
#Module(
complete = false,
library = true,
overrides = true
)
public final class DebugApiModule {
#Provides #Singleton Endpoint provideEndpoint(#ApiEndpoint StringPreference apiEndpoint) {
return Endpoints.newFixedEndpoint(apiEndpoint.get());
}
#Provides #Singleton MockRestAdapter provideMockRestAdapter(RestAdapter restAdapter, SharedPreferences preferences) {
MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter);
AndroidMockValuePersistence.install(mockRestAdapter, preferences);
return mockRestAdapter;
}
#Provides #Singleton MyApi provideMyApi(RestAdapter restAdapter, MockRestAdapter mockRestAdapter,
#IsMockMode boolean isMockMode, MockMyApi mockService) {
if (isMockMode) {
return mockRestAdapter.create(MyApi.class, mockService);
}
return restAdapter.create(MyApi.class);
}
}
But while I'm running tests, I would like to override the DebugApiModule with a TestApiModule that looks like this:
#Module(
complete = false,
library = true,
overrides = true
)
public final class TestApiModule {
#Provides #Singleton Endpoint provideEndpoint(#ApiEndpoint StringPreference apiEndpoint) {
return Endpoints.newFixedEndpoint(apiEndpoint.get());
}
#Provides #Singleton MockRestAdapter provideMockRestAdapter(RestAdapter restAdapter, SharedPreferences preferences) {
MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter);
mockRestAdapter.setDelay(0);
mockRestAdapter.setErrorPercentage(0);
mockRestAdapter.setVariancePercentage(0);
return mockRestAdapter;
}
#Provides #Singleton MyApi provideMyApi(MockRestAdapter mockRestAdapter, MockHnApi mockService) {
return mockRestAdapter.create(MyApi.class, mockService);
}
}
What's the best way to accomplish this? Do I need to create a TestAppModule like this:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app),
new TestAppModule()
};
}
private Modules() {
// No instances.
}
}
And replace all of the DebugFooModule with TestFooModules? If so, how do I get around the fact that Modules.java is duplicated? Or am I way off base?
EDIT: SOLUTION
What I ended up doing is replacing the Application-level graph (where the MockRestAdapter gets created) during my test setUp
protected void setUp() throws Exception {
super.setUp();
HnApp app = HnApp.getInstance();
app.buildObjectGraphAndInject(TestModules.list(app));
getActivity();
}

What I've done this far (and I'm not entirely sure this will scale yet) is create a static test module class in my tests.
#Module(
injects = {
SharedPreferences.class
})
static class TestModule {
#Provides #Singleton SharedPreferences provideSharedPreferences(){
return Mockito.mock(SharedPreferences.class);
}
}
And in the setup() method I inject the test module
#Before
public void setUp(){
ObjectGraph.create(new TestModule()).inject(this);
}
I then #Inject the mocked class:
#Inject SharedPreferences sharedPreferences;

Related

How to test screens which requires authorization? Espresso + MockWebServer

I am creating UI tests. In order not to interact with the real server, I use MockWebServer. My goal is to emulate various server responses and see how the program as a whole will respond to them. At the moment, I don’t understand how to open screens that require authorization. Of course, I can write a code that will login to the authorization screen, and then go to the desired window. But this requires additional time to complete the test, and I would like to avoid this. I would not want to mocking classes, because I need to check the production version of the application. How can i do this?
For DI, I use Dagger-2. Here is the component code:
#Singleton
#Component(modules = {
AvatarsModule.class,
EncryptionModule.class,
ApiModule.class,
WalletsModule.class,
GeneralModule.class,
InteractorsModule.class,
PushNotificationsModule.class,
AppModule.class
})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder context(Context context);
AppComponent build();
}
void inject(App app);
}
Here is the class code in which the authorization state is stored:
public class ApiWrapper {
private Api api;
private KeyPair keyPair;
private Account account;
...
public Flowable<Authorization> authorize(KeyPair tempKeyPair) {
return api
.authorize(tempKeyPair.getPublicKeyString().toLowerCase())
.subscribeOn(Schedulers.io())
.doOnNext((authorization -> {
this.account = authorization.getAccount();
this.keyPair = tempKeyPair;
}));
}
...
}
If anyone is still interested. I wrote an InstrumentationTestFacade class in which I put an ApiWrapper object using Dagger. Next, the InstrumentationTestFacade is injected into the Application object. Since the application object is not a singleton, there is no leak of responsibility in the main code, but from the test code you can access this facade using the following code:
Application application = (Application) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
InstrumentationTestFacade facade = application.getInstrumentationTestFacade();
Below is an example:
public class InstrumentationTestFacade {
private LogoutInteractor logoutInteractor;
private SecurityInteractor securityInteractor;
private ApiWrapper apiWrapper;
public InstrumentationTestFacade(
LogoutInteractor logoutInteractor,
SecurityInteractor securityInteractor,
ApiWrapper apiWrapper
) {
this.logoutInteractor = logoutInteractor;
this.securityInteractor = securityInteractor;
this.apiWrapper = apiWrapper;
}
public void logout() {
logoutInteractor.logout();
}
public ApiWrapper getApiWrapper() {
return apiWrapper;
}
public SecurityInteractor getSecurityInteractor() {
return this.securityInteractor;
}
}
public class Application extends MultiDexApplication implements HasActivityInjector, HasServiceInjector {
...
#Inject
InstrumentationTestFacade instrumentationTestFacade;
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent
.builder()
.context(this)
.build()
.inject(this);
}
...
public InstrumentationTestFacade getInstrumentationTestFacade() {
return instrumentationTestFacade;
}
}

Mock dependent component's dependency with DaggerMock

I want to write Espresso tests for an app so I'm trying DaggerMock to
mock some external dependencies like local storage.
My Dagger setup consists of an ApplicationComponent with 3 modules (DatabaseModule, DataModule and ApplicationModule) and for the screen( a Fragment ) I want to test I have also another component which depends on ApplicationComponent.
What I have tried so far is :
#Rule public DaggerMockRule<ApplicationComponent> daggerRule =
new DaggerMockRule<>(ApplicationComponent.class, new DatabaseModule(), new DataModule(application),
new ApplicationModule(application)).set(
component -> {
MyApplication app =
(MyApplication) InstrumentationRegistry.getInstrumentation()
.getTargetContext()
.getApplicationContext();
app.setComponent(component);
});
#Rule
public final DaggerMockRule<FeedComponent> rule = new DaggerMockRule<>(
FeedComponent.class, new FeedDataSourceModule(),
new FeedDownloadImageUseCaseModule(), new FeedServiceModule(), new FeedPresenterModule(null))
.addComponentDependency(ApplicationComponent.class, new DatabaseModule(), new DataModule(application), new ApplicationModule(application))
.set(component -> localDataSource = component.localDataSource());
#Mock FeedDao feedDao;
#Mock NetworkUtils networkUtils;
#Mock FeedLocalDataSource localDataSource;
where localDataSource is actually the dependency I want to mock and it's build in FeedDataSourceModule :
#Module
public class FeedDataSourceModule {
#Provides
#FragmentScope
public FeedItemMapper providesFeedItemMapper() {
return new FeedItemMapper();
}
#Provides
#FragmentScope
public FeedLocalDataSource providesFeedLocalDataSource(FeedDao feedDao, FeedRequestDetailsDao detailsDao, FeedItemMapper mapper) {
return new FeedLocalDataSourceImpl(feedDao, detailsDao, mapper);
}
#Provides
#FragmentScope
public FeedRemoteDataSource providesFeedRemoteDataSource(FeedService feedService, FlagStateService flagStateService,
#Named("Api-Token") String apiToken, #Named("Screen-Size") String screenSize,
#Named("Account-Id") String accountId) {
return new FeedRemoteDataSourceImpl(feedService, flagStateService, apiToken, screenSize, accountId);
}
}
and also the FeedComponent with the dependency on ApplicationComponent :
#FragmentScope
#Component( dependencies = ApplicationComponent.class,
modules = {
FeedPresenterModule.class,
FeedServiceModule.class,
FeedDataSourceModule.class,
FeedDownloadImageUseCaseModule.class})
public interface FeedComponent {
#Named("Api-Token") String getApiToken();
#Named("Api-Key") String getApiKey();
FeedLocalDataSource localDataSource();
FeedRemoteDataSource remoteDataSource();
void inject(FeedFragment feedFragment);
}
With the two #Rules I posted above I can confirm that NetworkUtils indeed seems to have been mocked correctly since I have used Mockito.when() to return false value and by using a breakpoint in my code I can see the value is always false :
when(networkUtils.isOnline())
.thenReturn(false);
But this is not true for localDataSource which gives me null when I'm calling localDataSource.getFeedSorted() although I have declared :
when(localDataSource.getFeedSorted())
.thenReturn(Flowable.just(feedList));
Just in case it helps, this is how I inject the dependencies from FeedComponent :
DaggerFeedComponent.builder()
.applicationComponent(MyApplication.getApplicationComponent())
.feedPresenterModule(new FeedPresenterModule(this))
.build()
.inject(this);
Why are you using two DaggerMock rules in a test? I think you can use a single rule like in this example.

How to mock sharedpreferences for android instrumentation tests?

I have a preference util class to store and retrieve the data in shared preferences in a single place.
Prefutils.java:
public class PrefUtils {
private static final String PREF_ORGANIZATION = "organization";
private static SharedPreferences getPrefs(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
private static SharedPreferences.Editor getEditor(Context context) {
return getPrefs(context).edit();
}
public static void storeOrganization(#NonNull Context context,
#NonNull Organization organization) {
String json = new Gson().toJson(organization);
getEditor(context).putString(PREF_ORGANIZATION, json).apply();
}
#Nullable public static Organization getOrganization(#NonNull Context context) {
String json = getPrefs(context).getString(PREF_ORGANIZATION, null);
return new Gson().fromJson(json, Organization.class);
}
}
Sample code showing PrefUtils usage in LoginActivity.java:
#Override public void showLoginView() {
Organization organization = PrefUtils.getOrganization(mActivity);
mOrganizationNameTextView.setText(organization.getName());
}
List of androidTestCompile dependencies in build.gradle:
// Espresso UI Testing dependencies.
androidTestCompile "com.android.support.test.espresso:espresso-core:$project.ext.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-contrib:$project.ext.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-intents:$project.ext.espressoVersion"
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2:'
src/androidTest/../LoginScreenTest.java
#RunWith(AndroidJUnit4.class) #LargeTest public class LoginScreenTest {
#Rule public ActivityTestRule<LoginActivity> mActivityTestRule =
new ActivityTestRule<>(LoginActivity.class);
#Before public void setUp() throws Exception {
when(PrefUtils.getOrganization(any()))
.thenReturn(HelperUtils.getFakeOrganization());
}
}
The above code to return fakeOrganization was not working, running the tests on login activity results in NullPointerException in line mOrganizationNameTextView.setText(organization.getName()); defined in the above LoginActivity.java class.
How to solve the above issue?
Approach-1:
Expose SharedPreference with application scope using Dagger2 and use it like #Inject SharedPreferences mPreferences in activity/fragment.
Sample code using the above approach to save(write) a custom preference:
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(PREF_ORGANIZATION, mGson.toJson(organization));
editor.apply();
To read a custom preference:
String organizationString = mPreferences.getString(PREF_ORGANIZATION, null);
if (organizationString != null) {
return mGson.fromJson(organizationString, Organization.class);
}
If you use it like above it results in breaking the DRY principle, since the code will be repeated in multiple places.
Approach-2:
This approach is based on the idea of having a separate preference class like StringPreference/ BooleanPreference which provides wrapper around the SharedPreferences code to save and retrieve values.
Read the below posts for detailed idea before proceeding with the solution:
Persist your data elegantly: U2020 way by #tasomaniac
Espresso 2.1: ActivityTestRule by chiuki
Dagger 2 + Espresso 2 + Mockito
Code:
ApplicationModule.java
#Module public class ApplicationModule {
private final MyApplication mApplication;
public ApplicationModule(MyApplication application) {
mApplication = application;
}
#Provides #Singleton public Application provideApplication() {
return mApplication;
}
}
DataModule.java
#Module(includes = ApplicationModule.class) public class DataModule {
#Provides #Singleton public SharedPreferences provideSharedPreferences(Application app) {
return PreferenceManager.getDefaultSharedPreferences(app);
}
}
GsonModule.java
#Module public class GsonModule {
#Provides #Singleton public Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
}
ApplicationComponent.java
#Singleton #Component(
modules = {
ApplicationModule.class, DataModule.class, GsonModule.class
}) public interface ApplicationComponent {
Application getMyApplication();
SharedPreferences getSharedPreferences();
Gson getGson();
}
MyApplication.java
public class MyApplication extends Application {
#Override public void onCreate() {
initializeInjector();
}
protected void initializeInjector() {
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
}
OrganizationPreference.java
public class OrganizationPreference {
public static final String PREF_ORGANIZATION = "pref_organization";
SharedPreferences mPreferences;
Gson mGson;
#Inject public OrganizationPreference(SharedPreferences preferences, Gson gson) {
mPreferences = preferences;
mGson = gson;
}
#Nullable public Organization getOrganization() {
String organizationString = mPreferences.getString(PREF_ORGANIZATION, null);
if (organizationString != null) {
return mGson.fromJson(organizationString, Organization.class);
}
return null;
}
public void saveOrganization(Organization organization) {
SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(PREF_ORGANIZATION, mGson.toJson(organization));
editor.apply();
}
}
Wherever you need the preference just inject it using Dagger #Inject OrganizationPreference mOrganizationPreference;.
For androidTest, I'm overriding the preference with a mock preference. Below is my configuration for android tests:
TestDataModule.java
public class TestDataModule extends DataModule {
#Override public SharedPreferences provideSharedPreferences(Application app) {
return Mockito.mock(SharedPreferences.class);
}
}
MockApplication.java
public class MockApplication extends MyApplication {
#Override protected void initializeInjector() {
mApplicationComponent = DaggerTestApplicationComponent.builder()
.applicationModule(new TestApplicationModule(this))
.dataModule(new TestDataModule())
.build();
}
}
LoginScreenTest.java
#RunWith(AndroidJUnit4.class) public class LoginScreenTest {
#Rule public ActivityTestRule<LoginActivity> mActivityTestRule =
new ActivityTestRule<>(LoginActivity.class, true, false);
#Inject SharedPreferences mSharedPreferences;
#Inject Gson mGson;
#Before public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
MyApplication app = (MyApplication) instrumentation.getTargetContext().getApplicationContext();
TestApplicationComponent component = (TestApplicationComponent) app.getAppComponent();
component.inject(this);
when(mSharedPreferences.getString(eq(OrganizationPreference.PREF_ORGANIZATION),
anyString())).thenReturn(mGson.toJson(HelperUtils.getFakeOrganization()));
mActivityTestRule.launchActivity(new Intent());
}
}
Make sure you have dexmaker mockito added in build.gradle
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2:'
Unfortunately Mockito cannot perform what you are looking for on its own. You have two options, one is to use Power Mock and the other is to change Prefutils into a normal class and instead use a Dependency Injection Framework.
Power Mock
Nice and simple, this will let you mock static methods, check out this SO post for details. On the downside it may result in other issues based on the comments in that SO post.
Dependency Injection Approach (my original answer)
You are trying to write a UI test with some of the behavior of the application "mocked". Mockito is built to let you write Unit tests where you test a specific object (or group of objects) and mock some of their behavior.
You can see some examples of how mockito is used in these tests (1, 2). None of them test the UI, instead they instantiate an object "stub"/"mock" some if its behavior and then test the rest.
To achieve what you want you will instead need a dependency injection framework. This allows you to change the "implementation" of some of your application based on whether you are running the actual application or a test.
The details of how you mock behavior of your classes/objects varies from framework to framework. This blog post goes over how to use Dagger 2 with Mockito and espresso you can apply the same approach for your tests. It also has links to presentations that give more background on dagger 2.
If you don't like dagger 2 then you can also checkout RoboGuice and Dagger. Just note, I do not think butter-knife will fit your needs as it doesn't support injection of Pojos.

Is this the correct way of adding parammeters to constructor in Dagger 2?

Context
Recenty I started investigating about dependency injection and Dagger 2. It looks a pretty good library but it seems a bit confusing to me. There are some situations in which I don't know exactly how to proceed.
What have I tried
I have created a simple Android app that creates a Client and its Dependency and do some (dummy) work. These are the classes:
Client.java
public class Client {
private Dependency dep;
#Inject
public Client(Dependency dep) {
this.dep = dep;
}
public void work() {
System.out.println("Client working");
dep.doWork();
}
}
Dependency.java
public class Dependency {
#Inject
public Dependency() {
}
public void doWork() {
System.out.println("Dependency working");
}
}
Following some tutorials I created a couple of Module classes:
DependencyModule.java
#Module
public class DependencyModule {
#Provides
Dependency provideDependency() {
return new Dependency();
}
}
ClientModule.java
#Module
public class ClientModule {
#Provides
Client provideClient(Dependency dep) {
return new Client(dep);
}
}
And also the Component interface:
#Component(modules = {ClientModule.class})
public interface ClientComponent {
Client provideClient();
}
This works fine. From my activity I can do the following and it works:
ClientComponent clientComp = DaggerClientComponent
.builder()
.clientModule(new ClientModule())
.build();
Client client = clientComp.provideClient();
client.work();
Problem
I understand how to inject dependencies in a client (at least I think so). But how I add parameters into the constructor of a client/dependency?
I mean, what if I would wanted to add some int parameters to my objects? Something as simple as this:
Client.java
public class Client {
int id;
Dependency dep;
#Inject
public Client(int id, Dependency dep) {
this.id = id;
this.dep = dep;
}
public void work() {
System.out.println("id: " + id + " Client working");
dep.doWork();
}
}
Dependency.java
public class Dependency {
private int id;
#Inject
public Dependency(int id) {
this.id = id;
}
public void doWork() {
System.out.println("id: " + id + " Dependency working");
}
}
NOTE:
The following code is what I've tried. So I'm not sure about its correctness.
So, as the objects has new parameters in their constructor the Modules have to change:
DependencyModule.class
public class DependencyModule {
#Provides
Dependency provideDependency() {
return new Dependency(id);
}
}
ClientModule.class
#Module
public class ClientModule {
#Provides
Client provideClient(int id, Dependency dep) {
return new Client(id, dep);
}
}
Question
How do I use that new Modules? I haven't found a way to pass the id to that methods. The only way I get it to work is by passing it in the Module constructor and removing it from the provide method. This way:
#Module
public class ClientModule {
private int id;
public ClientModule(int id) {
this.id = id;
}
#Provides
Client provideClient(Dependency dep) {
return new Client(id, dep);
}
}
Same approach in the DependencyModule.java.
This way, adding the DependencyModule.class in the ClientComponent interface I can do something like:
ClientComponent clientComp = DaggerClientComponent
.builder()
.clientModule(new ClientModule(clientId))
.dependencyModule(new DependencyModule(dependencyId))
.build();
Client client = clientComp.provideClient();
client.work();
Is that the correct way of doing that?
Is there a better way of getting the same effect?
Am I committing crimes against DI principle?
There are two basic ways to get Dagger to provide an instance of a class:
Add #Inject to a constructor, and put the class's dependencies in as constructor arguments.
Add a #Provides-annotated method to a #Module-annotated class, and install that module into your #Component.
You only need to use one method for each class. So in your first example, Client and Dependency are fine as is; you don't also need ClientModule and DependencyModule.
Once you add the int dependency, now you do need a module, because there's no class to #Inject. The module just needs to provide that int, so something like this would work:
#Module
public class ClientIdModule {
private final clientId;
public ClientIdModule(int clientId) {
this.clientId = clientId;
}
#Provides
static int clientId() {
return clientId;
}
}
Now if you install ClientIdModule into your component, you'll be able to get a Client which has the right ID, and its Dependency will as well.

How can I replace Activity scoped dependencies with mocks using Dagger2

I have a scoped dependency in my Activity and I want to test that activity with some mocks. I have read about different approach that suggest to replace Application component with a test component during the test, but what I want is to replace the Activity component.
For example, I want to test the Activity against mock presenter in my MVP setup.
I believe that replacing component by calling setComponent() on Activity will not work, because Activity dependencies already injected via field injection, so during the test, real object will be used.
How can I resolve this issue? What about Dagger1? Is it has the same issue?
Injecting the Component
First, you create a static class to act as a factory for your Activity. Mine looks a little like this:
public class ActivityComponentFactory {
private static ActivityComponentFactory sInstance;
public static ActivityComponentFactory getInstance() {
if(sInstance == null) sInstance = new ActivityComponentFactory();
return sInstance;
}
#VisibleForTesting
public static void setInstance(ActivityComponentFactory instance) {
sInstance = instance;
}
private ActivityComponentFactory() {
// Singleton
}
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.create();
}
}
Then just do ActivityComponentFactory.getInstance().createActivityComponent().inject(this); inside your Activities.
For testing, you can replace the factory in your method, before the Activity is created.
Providing mocks
As #EpicPandaForce's answer makes clear, doing this the officially-supported way currently involves a lot of boilerplate and copy/pasted code. The Dagger 2 team need to provide a simpler way of partially overriding Modules.
Until they do though, here's my unnoficial way: Just extend the module.
Let's say you want to replace your ListViewPresenter with a mock. Say you have a PresenterModule which looks like this:
#Module #ActivityScope
public class PresenterModule {
#ActivityScope
public ListViewPresenter provideListViewPresenter() {
return new ListViewPresenter();
}
#ActivityScope
public SomeOtherPresenter provideSomeOtherPresenter() {
return new SomeOtherPresenter();
}
}
You can just do this in your test setup:
ActivityComponentFactory.setInstance(new ActivityComponentFactory() {
#Override
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.builder()
.presenterModule(new PresenterModule() {
#Override
public ListViewPresenter provideListViewPresenter() {
// Note you don't have to use Mockito, it's just what I use
return Mockito.mock(ListViewPresenter.class);
}
})
.build();
}
});
...and it just works!
Note that you don't have to include the #Provides annotation on the #Override method. In fact, if you do then the Dagger 2 code generation will fail.
This works because the Modules are just simple factories - the generated Component classes take care of caching instances of scoped instances. The #Scope annotations are used by the code generator, but are irrelevant at runtime.
You cannot override modules in Dagger2 [EDIT: you can, just don't specify the #Provides annotation on the mock), which would obviously be the proper solution: just use the builder().somethingModule(new MockSomethingModule()).build() and be done with it!
If you thought mocking is not possible, then I would have seen two possible solutions to this problem. You can either use the modules to contain a pluggable "provider" that can have its implementation changed (I don't favor this because it's just too verbose!)
public interface SomethingProvider {
Something something(Context context);
}
#Module
public class SomethingModule {
private SomethingProvider somethingProvider;
public SomethingModule(SomethingProvider somethingProvider) {
this.somethingProvider = somethingProvider;
}
#Provides
#Singleton
public Something something(Context context) {
return somethingProvider.something(context);
}
}
public class ProdSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new SomethingImpl(context);
}
}
public class TestSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new MockSomethingImpl(context);
}
}
SomethingComponent somethingComponent = DaggerSomethingComponent.builder()
.somethingModule(new SomethingModule(new ProdSomethingProvider()))
.build();
Or you can bring the provided classes and injection targets out into their own "metacomponent" interface, which your ApplicationComponent and your TestApplicationComponent extend from.
public interface MetaApplicationComponent {
Something something();
void inject(MainActivity mainActivity);
}
#Component(modules={SomethingModule.class})
#Singleton
public interface ApplicationComponent extends MetaApplicationComponent {
}
#Component(modules={MockSomethingModule.class})
#Singleton
public interface MockApplicationComponent extends MetaApplicationComponent {
}
The third solution is to just extend the modules like in #vaughandroid 's answer. Refer to that, that is the proper way of doing it.
As for activity scoped components... same thing as I mentioned here, it's just a different scope, really.
I've found the following post that solves the problem:
http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html
You need first to allow to modify the component of the activity:
#Override public void onCreate() {
super.onCreate();
if (component == null) {
component = DaggerDemoApplication_ApplicationComponent
.builder()
.clockModule(new ClockModule())
.build();
}
}
public void setComponent(DemoComponent component) {
this.component = component;
}
public DemoComponent component() {
return component;
}
And modify it in the test case
#Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
DemoApplication app
= (DemoApplication) instrumentation.getTargetContext().getApplicationContext();
TestComponent component = DaggerMainActivityTest_TestComponent.builder()
.mockClockModule(new MockClockModule())
.build();
app.setComponent(component);
component.inject(this);
}

Categories

Resources