Does Dagger support dependancy injection for ActivityInstrumentationTestCase2 tests - android

I am trying to use Dagger in an Android functional test which inherits ActivityInstrumentationTestCase2.
The setup code looks like this:
#Override
protected void setUp() {
// TODO Auto-generated method stub
try {
super.setUp();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ObjectGraph.create(new TestModule()).inject(this);
this.activity = super.getActivity();
}`
The OnCreate method, which is fired by calling super.getActivity(), does not use the classes provided by the TestModule. But if I run my activity manually (outside of the testing context) then all the appropriate classes are provided/injected by my non-test module.

I found a way to use Dagger with ActivityInstrumentationTestCase2 by lazily creating the Object Graph. What I do is wait to create the Object Graph until the very first time a class wants to be injected, so as long as you add your modules before calling getActivity() (which starts the activity lifecycle for the activity under test) and use overrides = true in your test modules, this will work. Here's the relevant classes and snippets:
GraphHolder, as the name implies, holds the ObjectGraph object for us. We will make all calls to this class rather than directly to ObjectGraph.
public class GraphHolder {
private static GraphHolder sInstance;
private Object[] mModules;
private ObjectGraph mGraph;
private GraphHolder() {
}
public static GraphHolder getInstance() {
if (sInstance == null) {
sInstance = new GraphHolder();
}
return sInstance;
}
public void inject(Object object) {
if (mGraph == null) {
create();
}
mGraph.inject(object);
}
public <T> T get(Class<T> type) {
if (mGraph == null) {
create();
}
return mGraph.get(type);
}
public void addModules(Object... modules) {
if (mGraph != null) {
mGraph.plus(modules);
} else {
if (mModules == null) {
mModules = modules;
} else {
mModules = concatenate(mModules, modules);
}
}
}
private void create() {
mGraph = ObjectGraph.create(mModules);
mModules = null;
}
private Object[] concatenate(Object[] a, Object[] b) {
int aLength = a.length;
int bLength = b.length;
Object[] c = new Object[aLength + bLength];
System.arraycopy(a, 0, c, 0, aLength);
System.arraycopy(b, 0, c, aLength, bLength);
return c;
}
}
We'll add our modules in the Application class:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
GraphHolder.getInstance().addModules(getModules());
}
Object[] getModules() {
return new Object[]{
// your modules here
};
}
}
Inside the classes we want to inject, we'll simply call GraphHolder.getInstance().inject(this) rather than ObjectGraph.inject(this)
In our test modules, we'll provide the objects we want to override for testing and add overrides = true to the #Module annotation. This tells the object graph to prefer this module's providers over others if there's a conflict.
Then, in our tests:
#Inject Foo mFoo;
#Override
public void setUp() {
super.setUp();
GraphHolder.getInstance().addModules(new TestFooModule());
GraphHolder.getInstance().inject(this); // This is when the object graph will be created
}

ObjectGraph.create(new TestModule()).inject(this);
This code is trying to inject dependencies created by TestModule into your TestCase instead of the tested Activity. What you'd have to do here is
ObjectGraph.create(new TestModule()).inject(this.activity);

Related

How to return different value for a static method which is invoked during the flow during unit testing?

I'm trying to write unit testing for the following snippet.
class ABC {
int getMyValue(final Activity activity) {
if(MyClass.getInstance(activity).getValue() == 1) return 10;
else return 20;
}
void doSomething() {
}
}
I've tried something like this to test the doSomething function.
mABC = new ABC();
public void test_doSomething() {
doReturn(20).when(mABC).getMyValue();
//validate
}
How can I test getMyValue similarly? I would like to assert when the value is 1 it's returning me 10 and in all other cases, it's returning me 20.
I'm doing this in my android application. Is there any existing framework that can help me do this?
EDIT:
MyClass looks something like this
public class MyClass {
private static Context mContext;
public static getInstance(Context context) {
mContext = context;
return new MyClass();
}
private MyClass() {}
public void getDreamValue() {
Settings.Secure.getInt(mContext.getContentResolver(), "dream_val", -1);
}
}
You might consider modifying your MyClass as follows.
public class MyClass {
private static Context mContext;
// Create a private variable that holds the instance.
private Myclass instance;
public static getInstance(Context context) {
mContext = context;
if (instance == null)
instance = new MyClass(); // Assign the instance here
return instance;
}
private MyClass() {}
public void getDreamValue() {
Settings.Secure.getInt(mContext.getContentResolver(), "dream_val", -1);
}
}
Now, as you are using Robolectric, you can set the instance value to a mock as follows in your test class.
#RunWith(RobolectricTestRunner.class)
public class ABCTest {
#Mock
MyClass mockInstance;
#Mock
Context mockContext;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
// Set the mock instance for MyClass
ReflectionHelpers.setStaticField(MyClass.class, "instance", mockInstance);
}
#Test
public void testWhen1() {
doReturn(1).when(mockInstance).getDreamValue();
Assert.assertEquals(10, new ABC().getMyValue());
}
#Test
public void testWhenNot1() {
doReturn(2).when(mockInstance).getDreamValue();
Assert.assertEquals(20, new ABC().getMyValue());
}
#After
public void tearDown() {
// Set the instance to null again to enable further tests to run
ReflectionHelpers.setStaticField(MyClass.class, "instance", null);
}
}
I hope that helps.
Note: It looks like you are trying to provide a singleton instance of MyClass. Hence, you really should not create a new instance of MyClass in the getInstance function. I avoided creating a new instance each time, using the null check in my code.

How do I prevent Mortar scopes from persisting across screens?

I have an app set up using Mortar/Flow and Dagger 2. It seems to work except for when I switch between two views of the same class. The new view ends up with the previous view's presenter.
For example, I have a ConversationScreen that takes a conversationId as a constructor argument. The first time I create a ConversationScreen and add it to Flow it creates the ConversationView which injects itself with a Presenter which is created with the conversationId that was passed to the screen. If I then create a new ConversationScreen with a different conversationId, when the ConversationView asks for a Presenter, Dagger returns the old Presenter, because the scope has not yet closed on the previous ConversationScreen.
Is there a way for me to manually close the scope of the previous screen before I set up the new one? Or have I just set up the scoping wrong to begin with?
ConversationView
public class ConversationView extends RelativeLayout {
#Inject
ConversationScreen.Presenter presenter;
public ConversationView(Context context, AttributeSet attrs) {
super(context, attrs);
DaggerService.<ConversationScreen.Component>getDaggerComponent(context).inject(this);
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
presenter.takeView(this);
}
#Override
protected void onDetachedFromWindow() {
presenter.dropView(this);
super.onDetachedFromWindow();
}
}
ConversationScreen
#Layout(R.layout.screen_conversation)
public class ConversationScreen extends Paths.ConversationPath implements ScreenComponentFactory<SomeComponent> {
public ConversationScreen(String conversationId) {
super(conversationId);
}
#Override
public String getTitle() {
title = Conversation.get(conversationId).getTitle();
}
#Override
public Object createComponent(SomeComponent parent) {
return DaggerConversationScreen_Component.builder()
.someComponent(parent)
.conversationModule(new ConversationModule())
.build();
}
#dagger.Component(
dependencies = SomeComponent.class,
modules = ConversationModule.class
)
#DaggerScope(Component.class)
public interface Component {
void inject(ConversationView conversationView);
}
#DaggerScope(Component.class)
#dagger.Module
public class ConversationModule {
#Provides
#DaggerScope(Component.class)
Presenter providePresenter() {
return new Presenter(conversationId);
}
}
#DaggerScope(Component.class)
static public class Presenter extends BasePresenter<ConversationView> {
private String conversationId;
#Inject
Presenter(String conversationId) {
this.conversationId = conversationId;
}
#Override
protected void onLoad(Bundle savedInstanceState) {
super.onLoad(savedInstanceState);
bindData();
}
void bindData() {
// Show the messages in the conversation
}
}
}
If you use the default ScreenScoper and PathContextFactory classes from Mortar/Flow example project, you will see that the name of the new scope to create is the name of the Screen class.
Because you want to navigate from one instance of ConversationScreen to another instance of ConversationScreen, the name of the new scope will be equal to the name of previous scope. Thus, you won't create a new Mortar scope but just reuse the previous one, which means reusing the same presenter.
What you need is to change the naming policy of the new scope. Rather than using only the name of the new screen class, add something else.
Easiest fix is to use the instance identifier: myScreen.toString().
Another better fix is to have a tracking of the screen/scope names.
Following example extracted from https://github.com/lukaspili/Mortar-architect
class EntryCounter {
private final SimpleArrayMap<Class, Integer> ids = new SimpleArrayMap<>();
int get(History.Entry entry) {
Class cls = entry.path.getClass();
return ids.containsKey(cls) ? ids.get(cls) : 0;
}
void increment(History.Entry entry) {
update(entry, true);
}
void decrement(History.Entry entry) {
update(entry, false);
}
private void update(History.Entry entry, boolean increment) {
Class cls = entry.path.getClass();
int id = ids.containsKey(cls) ? ids.get(cls) : 0;
ids.put(cls, id + (increment ? 1 : -1));
}
}
And then use this counter when creating new scope:
private ScopedEntry buildScopedEntry(History.Entry entry) {
String scopeName = String.format("ARCHITECT_SCOPE_%s_%d", entry.path.getClass().getName(), entryCounter.get(entry));
return new ScopedEntry(entry, MortarFactory.createScope(navigator.getScope(), entry.path, scopeName));
}
And in some other place, i'm incrementing/decrementing the counter if new scope is pushed or scope is detroyed.
The scope in ScreenScoper is based on a string, which if you create the same path, it will use the same name as it bases it on the class name of your path.
I solved this by removing some noise from the ScreenScoper, considering I'm not using #ModuleFactory in my Dagger2-driven project anyways.
public abstract class BasePath
extends Path {
public abstract int getLayout();
public abstract Object createComponent();
public abstract String getScopeName();
}
public class ScreenScoper {
public MortarScope getScreenScope(Context context, String name, Object screen) {
MortarScope parentScope = MortarScope.getScope(context);
return getScreenScope(parentScope, name, screen);
}
/**
* Finds or creates the scope for the given screen.
*/
public MortarScope getScreenScope(MortarScope parentScope, final String name, final Object screen) {
MortarScope childScope = parentScope.findChild(name);
if (childScope == null) {
BasePath basePath = (BasePath) screen;
childScope = parentScope.buildChild()
.withService(DaggerService.TAG, basePath.createComponent())
.build(name);
}
return childScope;
}
}

Dagger Inject different Dependency to IntentService in prod & test

Is it possible to inject different object through dagger into android.app.IntentService depending if it is a test or production?
this is mainly the code (simplified) which injects the WebRequest Class into the Service.
public class SomeService extends android.app.IntentService {
#Inject
WebReqeust mWebRequest;
public SomeService(String name) {
super(name);
MainApplication.getInstance().inject(this);
}
#Override
protected void onHandleIntent(Intent intent) {
String json = mWebRequest.getHttpString(url);
JSONObject o = new JSONObject(json);
DBHelper.insert(o);
}
}
#Module(injects = { SomeService.class })
public class WebRequestModule {
#Provides
WebRequest provideWebRequest() {
return new WebRequest();
}
}
public class Modules {
public static Object[] list() {
return new Object[] {
new WebRequestModule()
};
}
}
public class MainApplication extends Application {
private ObjectGraph mOjectGraph;
private static MainApplication sInstance;
#Override
public void onCreate() {
sInstance = this;
mOjectGraph = ObjectGraph.create(Modules.list());
}
public void inject(Object dependent) {
mOjectGraph.inject(dependent);
}
public void addToGraph(Object module) {
mOjectGraph.plus(module);
}
}
I would like to write a test which mocks the http response.
I've started with a new Module
#Module(
injects = SomeService.class,
overrides = true
)
final class MockTestModule {
#Provides
WebRequest provideWebRequest() {
WebRequest webRequest = mock(WebRequest.class);
when(webRequest.getJSONObjectResponse(contains("/register/"))).thenReturn(
new JSONObject(FileHelper.loadJSONFromAssets(this.getClass(),
"mock_register.json")));
when(webRequest.getJSONObjectResponse(contains("/register_validate/"))).thenReturn(
new JSONObject(FileHelper.loadJSONFromAssets(this.getClass(),
"mock_register_validate.json")));
return webRequest;
}
}
And in the test i tried the following
public class RegisterTest extends AndroidTestCase {
protected void setUp() throws Exception {
MainApplication.getInstance().addToGraph(new MockTestModule());
super.setUp();
}
public void test_theActuallTest() {
Registration.registerUser("email#email.com"); // this will start the service
wait_hack(); // This makes the test wait for the reposen form the intentservice, works fine
DBHelper.isUserRegisterd("email#email.com"));
}
}
The test is executed successfull (remember, the code is simplyfied and might not compile, just should represent the idea).
However, it still uses the "real" WebRequest Impl., not the Mocked one. I see it in the logs, the proxy and of ourse on the server ...
I did this with RoboGuice in a very similar way and it was working.
But somehow i am not able to get this done with dagger.
(I'm currently evaluating DI Frameworks and this is a "must have")
The plus method actual returns the new graph. It doesn't override the original graph. That being said to accomplish what you want you can simply do this.
public class MainApplication extends Application {
...
// Mostly used for testing
public void addToGraph(Object module) {
mObjectGraph = mOjectGraph.plus(module);
}
}
This takes the original graph and pluses it with your new module and then simply assigns the new graph to your mObjectGraph reference.

RoboGuice with Standard Android JUnit test cases

I want to use RoboGuice in a standard Android JUnit instrumentation test case and override one piece of my app's actual wiring with a mock for testing. I can't find anything online that explains how to do this as all of my search results go to Robolectric with RoboGuoice. I am not using Robolectric nor can I use it in my app for various reasons. Has anyone wired an app with RoboGuice and injected mocks for standard Android Intrumentation test cases?
I'm using the Roboguice 3 and I solved this problem with the following setup and teardown methods within the standard ActivityInstrumentationTestCase2.
Obviously you would need to replace new TestModule() in the snippet below with your own test module class.
#Override
protected void setUp() throws Exception {
super.setUp();
Application app = (Application)getInstrumentation().getTargetContext()
.getApplicationContext();
RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE,
Modules.override(RoboGuice.newDefaultRoboModule(app))
.with(new TestModule()));
getActivity();
}
#Override
protected void tearDown() throws Exception {
RoboGuice.Util.reset();
super.tearDown();
}
I've managed to get it work in a simple usage way, you just bind dependencies inside rule using builder and may forget about them later, it will do everything by itself. You may think it's over engineered, but it's realy good for reusing if tyou have a many test classes with robo guice dependencies inside.
Usage in test classes looks like:
#Rule
public InjectWithMocksRule injectWithMocksRule = new InjectWithMocksRule(
this,
() -> new InjectRule
.BindingBuilder()
.add(MyClass.class, mockedClassImpl)
.add(SomeInterface.class, mockedInterfaceImpl));
I wrote helper class TestBindingModule:
public class TestBindingModule extends AbstractModule {
private HashMap<Class<?>, Object> bindings = new HashMap<Class<?>, Object>();
#Override
#SuppressWarnings("unchecked")
protected void configure() {
Set<Entry<Class<?>, Object>> entries = bindings.entrySet();
for (Entry<Class<?>, Object> entry : entries) {
bind((Class<Object>) entry.getKey()).toInstance(entry.getValue());
}
}
public void addBinding(Class<?> type, Object object) {
bindings.put(type, object);
}
public void addBindings(HashMap<Class<?>, Object> bindings) {
this.bindings.putAll(bindings);
}
public static void setUp(Object testObject, TestBindingModule module) {
Module roboGuiceModule = RoboGuice.newDefaultRoboModule(RuntimeEnvironment.application);
Module testModule = Modules.override(roboGuiceModule).with(module);
RoboGuice.getOrCreateBaseApplicationInjector(RuntimeEnvironment.application, RoboGuice.DEFAULT_STAGE, testModule);
RoboInjector injector = RoboGuice.getInjector(RuntimeEnvironment.application);
injector.injectMembers(testObject);
}
public static void tearDown() {
Application app = RuntimeEnvironment.application;
DefaultRoboModule defaultModule = RoboGuice.newDefaultRoboModule(app);
RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, defaultModule);
}
}
Than I use custom Rule to make it work easy:
public class InjectRule implements TestRule {
public interface BindingBuilderFactory {
BindingBuilder create();
}
public static class BindingBuilder {
private HashMap<Class<?>, Object> bindings = new HashMap<>();
public BindingBuilder add(Class<?> dependencyClass, Object implementation) {
bindings.put(dependencyClass, implementation);
return this;
}
HashMap<Class<?>, Object> buildBindings() {
return this.bindings;
}
}
private Object target;
private BindingBuilderFactory bindingBuilderFactory;
public InjectRule(Object target, BindingBuilderFactory bindingBuilderFactory) {
this.target = target;
this.bindingBuilderFactory = bindingBuilderFactory;
}
private void overrideTestInjections(Object target) {
TestBindingModule module = new TestBindingModule();
module.addBindings(this.bindingBuilderFactory.create().buildBindings());
TestBindingModule.setUp(target, module);
}
#Override
public Statement apply(Statement base, Description description) {
return new StatementDecorator(base);
}
private class StatementDecorator extends Statement {
private Statement baseStatement;
StatementDecorator(Statement b) {
baseStatement = b;
}
#Override
public void evaluate() throws Throwable {
before();
try {
baseStatement.evaluate();
} catch (Error e) {
throw e;
} finally {
after();
}
}
void after() {
TestBindingModule.tearDown();
}
void before() {
overrideTestInjections(target);
}
}
}
Also you may want to init mocks with #Mock annotation inside of your test classes, so you need another custom rule:
public class MockitoInitializerRule implements TestRule {
private Object target;
public MockitoInitializerRule(Object target) {
this.target = target;
}
#Override
public Statement apply(Statement base, Description description) {
return new MockitoInitializationStatement(base, target);
}
private class MockitoInitializationStatement extends Statement {
private final Statement base;
private Object test;
MockitoInitializationStatement(Statement base, Object test) {
this.base = base;
this.test = test;
}
#Override
public void evaluate() throws Throwable {
MockitoAnnotations.initMocks(test);
base.evaluate();
}
}
}
And, finaly, you want to combine them to mock mocks first and then set them as dependencies:
public class InjectWithMocksRule implements TestRule {
private final RuleChain delegate;
public InjectWithMocksRule(Object target, InjectRule.BindingBuilderFactory bindingBuilderFactory) {
delegate = RuleChain
.outerRule(new MockitoInitializerRule(target))
.around(new InjectRule(target, bindingBuilderFactory));
}
#Override
public Statement apply(Statement base, Description description) {
return delegate.apply(base, description);
}
}

What code is running when I call getSystemService() from my activity?

I'm trying to trace AOSP code from the grepcode site.
When I call getSystemService(Context.WIFI_P2P_SERVICE), it gets to the following code:
#Override public Object getSystemService(String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
And since WIFI_P2P_SERVICE declared as public static final String WIFI_P2P_SERVICE = "wifip2p";, if will not fall in one of the conditions and will go to the super.getSystemService(name);
Activity extends ContextThemeWrapper, the code there is:
#Override public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(mBase).cloneInContext(this);
}
return mInflater;
}
return mBase.getSystemService(name);
}
Here also, the required service name will not match, mBase is an instance of Context so the code in Context is:
public abstract Object getSystemService(String name);
which means that classes which extends from it must handle that functionality.
Well, Where my request is being treated?
As far as i know the implementation code of Context is under the package android.app with class name ContextImpl
Here is getSystemService from that class -
#Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
Edit -
The entry point for WIFI_P2P_SERVICE -
registerService(WIFI_P2P_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(WIFI_P2P_SERVICE);
IWifiP2pManager service = IWifiP2pManager.Stub.asInterface(b);
return new WifiP2pManager(service);
}});

Categories

Resources