I have an activity with a dependency:
public class MyActivity extends AppCompatActivity {
#Inject Dependency;
#Override
protected void onCreate(Bundle savedInstanceState) {
// inject
}
}
public class Dependency {
#Inject
public Dependency() {
//..
}
}
Since Dependency has an injected constructor, Dagger2 doesn't require a module to know how to instantiate it, which is super convenient.
My question is: For testing purposes, do I have to have an explicit module that provides Dependency in order to be able to mock it and provide a mock version of Dependency? or is there a way to mock Dependency without it?
I found a way without creating an explicit Module. Here's how I did it using Robolectric and Mockito:
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21)
public class MyActivityTest {
#Mock AppComponent mAppComponent;
#Mock private Dependency mDependency;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
// ***
// use the mock AppComponent to perform injections
// ***
doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
((MyActivity) invocation.getArguments()[0]).mDependecy = mDependecy;
return null;
}
}).when(mAppComponent).inject(any(MyActivity.class));
}
}
Related
I am using very simple and likely very common scenario. Here is my sample dependency:
public class MyDependency {
#Inject
public MyDependency(...) {
...
}
}
I am not listing the above in any module (that is, there is no #Provides for MyDependency).
My sample use case goes like this:
public class ThePresenter {
#Inject
MyDependency myDependency;
public ThePresenter() {
App.getInstance().getAppComponent().inject(this);
}
}
Now I'd like to mock my dependency in unit tests. I don't want to use modules overrides (that would mean I have to add #Provides for all my dependencies marked with #Inject constructors), test components etc. Is there any alternative but standard and simple approach for the problem?
You need to use constructor injection, rather than your injection site inside the Presenter class constructor. Expose your Presenter to dagger2 by adding the #Inject annotation on the constructor (like you have done with the dependency):
public class ThePresenter {
private final MyDependency myDependency;
#Inject public ThePresenter(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
This then allows inversion of control and supplying the dependency/mock.
Usage :
public class ThePresenterTest {
#Mock private MyDependency myDependency;
private ThePresenter presenter;
#Before public void setup() {
MocktioAnnotations.initMocks(this);
presenter = new ThePresenter(myDependency);
Mockito.when(myDependency.someMethod()).thenReturn(someValue);
....
}
}
Just mock it?
public class ThePresenterTest {
#Mock MyDependency myDependency;
private ThePresenter presenter;
#Before
public void setup() {
initMocks(this);
presenter = new ThePresenter();
}
}
I want to integrate Dagger2, but i cant' build my project, build failed with:
error: com.example.animalslibrary.ui.home.activity.HomeActivity cannot be provided without an #Inject constructor or from an #Provides-annotated method.
com.example.animalslibrary.ui.home.activity.HomeActivity is injected at
com.example.animalslibrary.AppComponent.injectsHomeActivity(homeActivity)
I looking for answer about 3 hours, and i asking you for help now.
My actions step by step:
1) Add depenceses to Gradle:
implementation 'com.google.dagger:dagger:2.7'
annotationProcessor 'com.google.dagger:dagger-compiler:2.7'
2) Create empty test class NetworkUtils:
public class NetworksUtils {
}
3) Create module for it:
#Module
public class NetworksModule {
#Provides
NetworksUtils provideNetworksUtils() {
return new NetworksUtils();
}
}
4) Create "connection" interface:
import dagger.Component;
#Component(modules = NetworksModule.class)
public interface AppComponent {
void injectsHomeActivity(HomeActivity homeActivity);
}
5) Create App class. I don't completle understand why i did this(teaching by guide),exactly i don't understand why i need to extend by Application. Maybe to create all components when application starts. DaggerAppComponent is red, because of failed while building, this class does't created yet.
public class App extends Application {
private static AppComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerAppComponent.create();
}
public static AppComponent getComponent() {
return component;
}
}
6) Add App to manifest:
<application
android:name="com.example.animalslibrary.ui.App"
...
7) Now i write my HomeActivity class...
public class HomeActivity extends AppCompatActivity implements
HomeContract.View {
...
#Inject
private NetwotkUtils netwotkUtils;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
...
App.getComponent().injectsHomeActivity(this);
...
}
8)...and goes down when try to build project.
I think my error about App, but i can't understand where and how fix it.
UPD:
This modification is not helped:
public class NetwotkUtils {
#Inject
public NetwotkUtils() {
}
}
There are two problems with your code and one is leading to error you're getting.
You must include your module into building here component = DaggerAppComponent.create();
Dagger is NOT injecting into private fields.
Example of p1:
DaggerAppComponent.builder()
.networkModule(new NetworkModule())
.build()
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.
Hi i've got a following Problem. I want to write android tests with espresso for the Ui and in order to have tests that are not flaky i want to mock my presenter.
I use Dagger in the App. My Configuration is as Following:
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
//some injections
//some providings
}
I have a Module for the Component
#Module
public class AppModule {
//providings for component
}
then i have also a component for the activities with a module for the component
#PerActivity
#Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
//inject activites
//provide subcomponents for activites
}
then i have subcomponents for my pages
#PerActivity
#Subcomponent(modules = InfoModule.class)
public interface InfoComponent {
void inject(DetailActivity activity);
}
and a module for the subcomponent
#Module
public class InfoModule {
#Provides
public DetailPresenter provideDetailPresenter(ShowDetailsUseCase showDetailsUseCase,
OtherUseCase getPoisUseCase,
AccountManager accountManager, Navigator
navigator) {
return new DetailPresenter(showDetailsUseCase, otherUseCase, accountManager, navigator);
}
}
and then the detail Activity Injects the DetailPresenter
public class DetailActivity extends BaseActivity {
#Inject
DetailPresenter mPresenter;
InfoComponent mComponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mComponent = getActivityComponent().provideInfoModule(new InfoModule());
mComponent.inject(this);
mPresenter.bindView(this);
mPresenter.onCreate(new PresenterBundle(getIntent().getExtras(), savedInstanceState));
}
//functionality of detailActiviy
}
then i have the presenter which uses constructor injection
public class DetailPresenter extends BasePresenter {
private ShowDetailsUseCase mDetailsUseCase;
private final OtherUseCase getPoisUseCase;
private AccountManager accountManager;
private Navigator navigator;
#Inject
public DetailPresenter(ShowDetailsUseCase getDetailsUseCase, OtherUseCase getPoisUseCase,
AccountManager
accountManager, Navigator navigator) {
this.mDetailsUseCase = getDetailsUseCase;
this.getPoisUseCase = gotherUseCase;
this.accountManager = accountManager;
this.navigator = navigator;
}
#Override
public void onCreate(#Nullable PresenterBundle bundle) {
super.onCreate(bundle);
//other things to do on initialization
((DetailView) getView()).showDetails(getDetailsFromUseCase());
}
}
Now in the test i want to do mock the presenter:
#RunWith(AndroidJUnit4.class)
public class DetailActivityTest {
#Rule
public final ActivityTestRule<DetailActivity> main = new ActivityTestRule<DetailActivity>(DetailActivity.class, true, false);
#Rule
public final DaggerMockRule<AppComponent> rule=new EspressoDaggerMockRule();
#Mock
DetailPresenter presenter; //does not work because #Inject constructor
#Test
public void locationTest() {
Details details = generateDetails();
launchActivity();
doAnswer(answer -> {
activity.showDetails(details);
return null;
}
).when(presenter).onCreate(any());
//espresso verify afterwards
}
}
but if i try to mock the following error shows:
java.lang.RuntimeException: Error while trying to override objects:
a.b.c.ui.mvp.presenter.DetailPresenter
You must define overridden objects using a #Provides annotated method instead of using #Inject annotation
does someone have an idea how I am able to mock the presenter even with #Inject constructor and dependencies.
I do not want to mock the data layer because then I have to mock database, apiClient, cacheData and so on. And some of the datalayer also have inject dependencies so i cannot mock them either.
Thank you in advance
The DetailPresenter class is created in the InfoModule, so you don't need the Inject annotation. The error you get is because using DaggerMock you can replace only the objects created in a module. In your example you are already creating it in a module, you just need to remove the Inject annotation.
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());