Mock injected ViewModel - android

I need to run Android instrumented test on StarterActivity. Here is how it goes
public class StarterActivity extends BaseActivity<ActivityStarterBinding> {
#Inject
protected StarterViewModel starterViewModel;
#Override
public int getContentView() {
return R.layout.activity_starter;
}
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getApplicationComponent().inject(this);
}
//...
}
And I call for starterViewModel method in onStart.
StarterViewModel is injected via a constructor:
public class StarterViewModel {
private final AuthDataModel authDataModel;
#Inject
public StarterViewModel(AuthDataModel authDataModel) {
this.authDataModel = authDataModel;
}
#NonNull
public Single<Boolean> isUserLoggedIn() {
return authDataModel.isUserLoggedIn();
}
}
I found this really nice approach Android testing using Dagger 2, Mockito and a custom JUnit rule. But it needs me to add #Provide method. And application component is going to become "God component" with dependencies on a bunch of Modules (or one "God module").
How can I mock in Espresso test without adding #Provide method and overriding it in tests?

Related

JUnit Test return lateinit property <nameOfInstance>has not been initialized on presenter

I wanna t create a simple JUnit testing an the test return an error like
kotlin.UninitializedPropertyAccessException: lateinit property
mInstance has not been initialized
my tests I will write in Java. Here is the test class. My presenter and all classes is written in Kotlin with Dagger2 and RxJava.
public class PhoneNumberPresenterTest {
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Mock
private PhoneNumberContract.View view;
#Inject
private PhoneNumberPresenter phoneNumberPresenter;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
phoneNumberPresenter = new PhoneNumberPresenter(this.view);
}
#Test
public void fetchValidDataShouldLoadIntoView() {
SendCodeRequest request = new SendCodeRequest(anyString());
GenericResponse<User> response = new GenericResponse<>();
when(phoneNumberPresenter.mUserService.sendCode(request))
.thenReturn(Observable.just(response));
}
}
In My Presenter I do that
class PhoneNumberPresenter() : PhoneNumberContract.Handler {
#Inject
lateinit var mUserService: UserService
init {
Application.mInstance.mComponent.inject(this)
}
//.....
mUserService.sendPhoneNumber().....
}
In AppComponent class that
#Singleton
#Component(modules = arrayOf(HttpModule::class, AppModule::class))
interface AppComponent {
fun inject(presenter: PhoneNumberPresenter)
}
And the error of test is that "lateinit property mUserService has not been initialized"
I don't use Dagger for unit testing if I can avoid it because I want to control what's a mock, what's a spy, etc.
You should not use any "Application.mInstance" with Dagger. It seems like you use Dagger before version 2.10 - you should upgrade to the latest and greatest! There is a whole section about Android on https://google.github.io/dagger/android
With Dagger 2.10 and upwards you can inherit from DaggerApplication and then use AndroidInjection.inject() for Activities and #Inject on the constructor for your own classes.
public class MyApplication extends DaggerApplication {
/**
* This is the same as extending from MultiDexApplication.
* #see android.support.multidex.MultiDexApplication
*/
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
#Override
public void onCreate() {
super.onCreate();
// ...
}
#Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().create(this);
}
}
Your activities should look like this:
public class YourActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
}
For testing of activities, maybe you should use a TestApplication instead of your real Application class? See https://android.jlelse.eu/testing-your-app-with-dagger-2-c91cdc0860fb for some hints about that.

Cant inject classes using Dagger on Android

I am beggining with Dagger, I am using 1.2 version of it, and I have the following scenario:
Module:
#Module(injects = {
AuthenticationService.class
})
public class ServiceModule {
#Provides
AuthenticationService provideAuthenticationService() {
return ServiceFactory.buildService(AuthenticationService.class);
}
}
On my Application class I create the ObjectGraph:
public class FoxyRastreabilidadeApplication extends Application {
private static FoxyRastreabilidadeApplication singleton;
#Override
public void onCreate() {
super.onCreate();
createObjectGraph();
singleton = this;
}
private void createObjectGraph() {
ObjectGraph.create(ServiceModule.class);
}
}
and finally, at my LoginActivity, I try to inject my AuthenticationService:
public class LoginActivity extends Activity implements LoaderCallbacks<Cursor> {
private UserLoginTask mAuthTask = null;
#Inject
AuthenticationService authenticationService;
}
At this point, when I try to access my AuthenticationService instance it is always null, meaning it wasnt injected at all, I debugged my provider method to be sure of it, so, the question is, am I missing something? If so, what is it?
You need to hold on to the ObjectGraph and ask it to inject your objects directly.
Add an instance variable to your custom Application subclass to hold on to the created ObjectGraph:
this.objectGraph = ObjectGraph.create(ServiceModule.class);
Add a convenience method to your Application to do injection:
public void inject(Object object) {
this.objectGraph.inject(object);
}
Call that method wherever you need injection to happen, for example in your Activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication)getApplication()).inject(this);
...
}

Activity graphs and non-found dependency

I'm starting using the dagger, like it pretty much, but now facing some difficulties. My scenario is as follows: there's an activity and a dependency for it. Dependency is injected to the activity, and requires a reference to that activity. Just like this:
public class MainActivity extends BaseActivity {
#Inject ScbeHelper scbeHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
}
public class ScbeHelper {
protected static final String TAG = "scbe_helper";
private BaseActivity activityContext;
#Inject
public ScbeHelper(BaseActivity context) {
this.activityContext = context;
}
}
I'm following dagger's example from the github for activity's graphs. So I've created a similar structure in my project. First, the BaseActivity class, from which MainActivity is inherited:
public abstract class BaseActivity extends Activity {
private ObjectGraph activityGraph;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
protoApp application = (protoApp) getApplication();
// Exception happens in next line, inside plus() method
activityGraph = application.getApplicationGraph().plus(getModules().toArray());
// Inject ourselves so subclasses will have dependencies fulfilled when this method returns.
activityGraph.inject(this);
((protoApp)getApplication()).inject(this);
}
protected List<Object> getModules() {
return Arrays.<Object>asList(new ActivityModule(this));
}
public void inject(Object object) {
activityGraph.inject(object);
}
}
And the module:
#Module(injects={MainActivity.class})
public class ActivityModule {
private final BaseActivity activity;
public ActivityModule(BaseActivity activity) {
this.activity = activity;
}
#Provides #Singleton BaseActivity provideActivity() {
return activity;
}
}
Now, the problem: No injectable members on com.example.proto.BaseActivity. Do you want to add an injectable constructor? required by public com.example.proto.ScbeHelper(com.example.proto.BaseActivity)
In other words, provider method ActivityModule.provideActivity() doesn't really provide the instance of BaseActivity for some reason, though in my understanding it's set up correctly. Does anyone see an error in my setup? Am I missing something in dagger's logic?
Thanks in advance!
I'm no Dagger expert, but you have 2 issues:
you have a cyclical dependency: you helper want to have the activity injected, your activity wants to have the helper injected. I don't think Dagger can resolve this
your activity tries to get injected twice, once with the activity-level graph, once with the application-level graph
Here's what I did to get it to work:
in ScbeHelper: remove the #Inject annotation
in BaseActivity: remove ((protoApp)getApplication()).inject(this);
in ActivityModule: remove your provideActivity method (it's not going to be used anymore), and add the following method:
#Provides #Singleton ScbeHelper provideScbeHelper() {
return new ScbeHelper(activity);
}
What this does is it provides your ScbeHelper with the context it needs, but leaves only 1 annotation-driven injection so dagger can resolve it. Hope this helps.

RoboGuice unit test injecting app module instead of test module

I am trying to write a JUnit test for an Android Service using RoboGuice 2.0. I have a test module that binds injected dependencies to Mockito mock objects. However, when I run the test, the real implementations from my app module get injected instead. Here is some of the relevant code:
MainApplication.java:
public class MainApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
RoboGuice.setBaseApplicationInjector(this, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(this), new MainModule());
startService(new Intent(this, NotificationService.class));
}
}
MainModule.java:
public class MainModule extends AbstractModule {
#Override
protected void configure() {
bind(IFooManager.class).to(FooManagerImpl.class).in(Scopes.SINGLETON);
}
}
NotificationService.java:
public class NotificationService extends RoboService {
#Inject
private NotificationManager notificationManager;
#Inject
private SharedPreferences prefs;
#Inject
private IFooManager fooManager;
private IFooListener listener = new FooListener();
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
fooManager.addListener(listener);
}
#Override
public void onDestroy() {
super.onDestroy();
fooManager.removeListener(listener);
}
private class FooListener implements IFooListener {
// Do stuff that fires Notifications
}
}
NotificationServiceTest.java:
public class NotificationServiceTest extends ServiceTestCase<NotificationService> {
#Mock
private MockFooManager fooManager;
#Mock
private MockSharedPreferences prefs;
public NotificationServiceTest() {
super(NotificationService.class);
}
public void testStart() {
startService(null);
verify(fooManager).addListener(isA(IFooListener.class));
}
public void testStop() {
shutdownService();
verify(fooManager).removeListener(isA(IFooListener.class));
}
#Override
protected void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
Application app = new MockApplication();
setApplication(app);
RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, new TestModule());
}
#Override
protected void tearDown() throws Exception {
super.tearDown();
RoboGuice.util.reset();
}
private class TestModule extends AbstractModule {
#Override
protected void configure() {
bind(Context.class).toInstance(getContext());
bind(IFooManager.class).toInstance(fooManager);
bind(SharedPreferences.class).toInstance(prefs);
}
}
}
MockFooManager and MockSharedPreferences are empty abstract implementations of IFooManager and SharedPreferences, needed because RoboGuice can't inject mocks of interfaces. I am using Mockito with Dexmaker to support bytecode generation for mocked classes. Also, I am not using Robolectric, I am running these tests on a device or in the emulator.
When I run this test, I get the error Wanted but not invoked: fooManager.addListener(isA(com.example.IFooListener)). After stepping through this with the debugger, I found that RoboGuice is injecting the dependencies from MainModule instead of TestModule, so the test is using FooManagerImpl instead of MockFooManager. I don't understand how RoboGuice even knows about MainModule in the test code.
Here are some other things I tried to fix this, but none had any effect:
Specify app modules in roboguice.xml instead of calling RoboGuice.setBaseApplicationInjector in MainApplication.onCreate
Use Modules.override when calling RoboGuice.setBaseApplicationInjector instead of just passing the module list directly.
How do I get RoboGuice to use TestModule and ignore MainModule in my unit test?
It seems like you are missing a call to do the injection in your NotificationServiceTest. This is done as follows:
RoboGuice.getInjector(app).injectMembers(this);
You will need to add this at some point after you set the base injector and before the tests are run.
Use
RoboGuice.overrideApplicationInjector(app,RoboGuice.newDefaultRoboModule(app), new TestModule())
instead of
RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, new TestModule());

Dagger and dependecies on provided classes

I am "Daggering" my Android app and I am facing a little problem that I don't know if it's me or framework's fault. If it's me I will be very disappointed by myself :)
I have the following class to provide:
#Singleton
public class XmlService {
private final DataCommitter<XmlWritable> mXmlCommitter;
private final DataReader<XmlPushable> mXmlReader;
private final ConcurrentObjectMonitor mConcObjMonitor;
#Inject
public XmlService(DataCommitter<XmlWritable> xmlCommitter,
DataReader<XmlPushable> xmlProvider,
ConcurrentObjectMonitor concObjMonitor) {
mXmlCommitter = xmlCommitter;
mXmlReader = xmlProvider;
mConcObjMonitor = concObjMonitor;
}
With the following (among the others) class:
public class XmlDataReader implements DataReader<XmlPushable> {
// No Singleton no more.
// Eager Singleton (Therefore it should be made thread safe)
//static XmlDataReader mInstance = new XmlDataReader();
[CUT]
protected XmlPullParser mXmlPullParser;
#Inject
public void XmlDataRead(XmlPullParser xmlPullParser) {
mXmlPullParser = xmlPullParser;
}
This class is referred in my XmlServiceModule like this:
#Module(complete = true)
public class XmlServiceModule {
#Provides DataReader<XmlPushable> provideDataReader(XmlDataReader dataReader) {
return dataReader;
}
}
Now, the question is, is it legal to Inject provided classes? Because I am getting an error with the #Inject on the XmlDataReader ctor: Cannot inject public void XmlDataRead(org.xmlpull.v1.XmlPullParser).
EDIT: sorry guys, there was an error in my sample code, but I will leave it as is, because it can be useful in order to understand Christian's answer below.
Actually, you have an error in your sample code. You have no injectable constructor - you cannot inject an instance method, only a constructor, and that method is never called.
You should have:
public class XmlDataReader implements DataReader<XmlPushable> {
protected final XmlPullParser mXmlPullParser;
#Inject
public XmlDataReader(XmlPullParser xmlPullParser) {
mXmlPullParser = xmlPullParser;
}
}
This way it's a constructor, and will be properly injected.
As to the overall approach, you're in the right direction. You #Provide DataReader<XmlPushable, and effectively bind XmlDataReader to it in your provides method. So far so good. Dagger, when asked for DataReader<XmlPushable> will use that binding, and will effectively pass-through the dependency and provide XmlDataReader to satisfy this.
XmlDataReader has an #Inject annotated constructor, so Dagger's analysis will see it as an "injectable type" so you don't need an #Provides to provide it. As long as you either have an XmlPullParser that either has an #Inject annotated constructor or is provided in an #Provides method on a module, what you've coded here seems good, but is not quite complete.
You don't have any entryPoints listed in your #Module, and you must have a class that will be the first thing you obtain from the ObjectGraph. So this code is reasonable as far as it goes, but incomplete. You need something that injects (through a field or constructor) DataReader<XmlPushable> For example, you might make a FooActivity which does this.
Consider:
public class XmlDataReader implements DataReader<XmlPushable> {
protected XmlPullParser mXmlPullParser;
#Inject public XmlDataReader(XmlPullParser xmlPullParser) {
mXmlPullParser = xmlPullParser;
}
}
#Module(entryPoints = { FooActivity.class, BarActivity.class })
public class XmlServiceModule {
#Provides DataReader<XmlPushable> provideDataReader(XmlDataReader dataReader) {
return dataReader;
}
#Singleton
#Provides XmlPullParserFactory parserFactory() {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
return factory;
}
#Provides XmlPullParser parser(XmlPullParserFactory factory) {
XmlPullParser xpp = factory.newPullParser();
}
}
public class YourApp extends Application {
private ObjectGraph graph;
#Override public void onCreate() {
super.onCreate();
graph = ObjectGraph.get(new ExampleModule(this));
}
public ObjectGraph graph() {
return objectGraph;
}
}
public abstract class BaseActivity extends Activity {
#Override protected void onCreate(Bundle state) {
super.onCreate(state);
((YourApp) getApplication()).objectGraph().inject(this);
}
}
class FooActivity extends BaseActivity {
#Inject DataReader<XmlPushable> reader;
#Override public void onCreate(Bundle bundle) {
super.onCreate(bundle);
// custom Foo setup
}
}
class BarActivity extends BaseActivity {
#Inject DataReader<XmlPushable> reader;
#Override public void onCreate(Bundle bundle) {
super.onCreate(bundle);
// custom Bar setup
}
}
This is probably close to what you want. All things are reachable from an entry point, and all things you depend on are bound. The two Activities are your "entry points" (roots in the object graph). When they are initialized, they ultimately call the ObjectGraph's inject(Object) method on themselves, which causes Dagger to inject any #Inject annotated fields. Those fields are the DataReader<XmlPushable> fields, which is satisfy by XmlDataReader, which is provided with an XmlPullParser, which is provided by using a XmlPullParser. It all flows down the dependency chain.
Small note - #Module(complete=true) is the default. You don't need to specify complete, unless it's incomplete and you specify false

Categories

Resources