Using mocked objects - android

I've just started unit testing on Android with Mockito - how do you get the class that you are testing on to use the mocked class/object instead of the regular class/object?

You can use #InjectMocks for the class you writing the test.
#InjectMocks
private EmployManager manager;
Then you can use #Mock for the class you are mocking. This will be the dependency class.
#Mock
private EmployService service;
Then write a setup method to make things available for your tests.
#Before
public void setup() throws Exception {
manager = new EmployManager();
service = mock(EmployService.class);
manager.setEmployService(service);
MockitoAnnotations.initMocks(this);
}
Then write your test.
#Test
public void testSaveEmploy() throws Exception {
Employ employ = new Employ("u1");
manager.saveEmploy(employ);
// Verify if saveEmploy was invoked on service with given 'Employ'
// object.
verify(service).saveEmploy(employ);
// Verify with Argument Matcher
verify(service).saveEmploy(Mockito.any(Employ.class));
}

By injecting the dependency:
public class ClassUnderTest
private Dependency dependency;
public ClassUnderTest(Dependency dependency) {
this.dependency = dependency;
}
// ...
}
...
Dependency mockDependency = mock(Dependency.class);
ClassUnderTest c = new ClassUnderTest(mockDependency);
You can also use a setter to inject the dependency, or even inject private fields directly using the #Mock and #InjectMocks annotations (read the javadoc for a detailed explanation of how they work).

Related

Getting null pointer object on hilt inject object in activity when run test cases

Please check below my Module class in which I have defined my object which need to be inject using Hilt
NVModule.kt
#Module
#InstallIn(SingletonComponent::class)
class NVModule {
#Provides
#Named("ProfileHelper")
fun abprovideProfileHelper(): ProfileHelper {
return ProfileHelper(AppController.getInstance())
}
}
And now please check my Interface by which i have used the EntryPoint to access my dependency injection outside the Activity/Fragment like Helper class.
#EntryPoint
#InstallIn(SingletonComponent.class)
public interface CommonHiltInterface {
#Named("ProfileHelper")
public ProfileHelper provideProfileHelper();
}
}
Now please check the my Activity class on which i have used the dependency injection like below and it is working fine here. Means getting dependency injection properly
public class HomeActivity extends BaseActivity{
private ActivityHomescreenBinding
activityHomescreenBinding;
private Activity context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
activityHomescreenBinding =
DataBindingUtil.inflate(getLayoutInflater(),
R.layout.activity_homescreen, null, false);
setContentView(activityHomescreenBinding.getRoot());
CommonHiltInterface commonHiltInterface = EntryPointAccessors.fromApplication(context, CommonHiltInterface.class);
commonHiltInterface.provideProfileHelper().setData();
}
}
But in case of the Test cases , dependency injection getting NullPointerException . I am using the Robolectric for the test cases. Please check my below lines of code for the RobolectricTest case.
#HiltAndroidTest
#RunWith(RobolectricTestRunner.class)
#Config(application = HiltTestApplication.class,
sdk = Build.VERSION_CODES.N, manifest = Config.NONE)
public class HomeActivityTest {
#Rule
public HiltAndroidRule hiltRule = new
HiltAndroidRule(this);
#Before
public void setUp() throws Exception {
shadowOf(Looper.getMainLooper()).idle();
hiltRule.inject();
activity =
Robolectric.buildActivity(HomeActivity.class).
create().resume().get();
}
}
Note :- 1). I have also use #HiltAndroidApp() for application class.and using 2.36 version for hilt dependency
2). My dependency injection working fine for the Java classes like Activity/Fagment and Helper classes , But not working in test cases.
Please check my dependency for Hilt are as follow
testImplementation 'com.google.dagger:hilt-android-testing:2.36'
kaptTest 'com.google.dagger:hilt-android-compiler:2.36'
testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.36'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.36'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.36'
Application runs successfully but in case of Test case I am getting the null pointer exception below lines of code in Activity (HomeActivity).
CommonHiltInterface commonHiltInterface = EntryPointAccessors.fromApplication(context, CommonHiltInterface.class);
commonHiltInterface.provideProfileHelper().setData();

I am unable to test SharedPreference using Mockito

I am unable to mock sharedPreference and when i test my presenter then sharepreference instance is null.
#RunWith(MockitoJUnitRunner.class)
public class PreferencesPresenterTest {
#Mock
PreferencesMvpView preferencesMvpView;
#Mock
ApiService apiService;
#Mock
Context context;
#Mock
SchedulerProvider mSchedulerProvider;
PreferencesPresenter mPresenter;
#Before
public void setUp() throws Exception {
CompositeDisposable compositeDisposable = new CompositeDisposable();
mPresenter = new PreferencesPresenter(compositeDisposable, apiService, mSchedulerProvider);
// mPrefences = new AppPreferences();
mPresenter.onAttach(preferencesMvpView);
}
#Test
public void testFilter() throws Exception {
Mockito.when(mSchedulerProvider.getUiScheduler()).thenReturn(Schedulers.trampoline());
Mockito.when(mSchedulerProvider.getWorkerScheduler()).thenReturn(Schedulers.trampoline());
mPresenter.loadPreferenceData();
}
}
//This is the method which i am testing
#Override
public void loadPreferenceData() {
long userId = mPreferences.getLong(AppPreferences.USER_ID);
getMvpView().showLoading();
getCompositeDisposable().add(getApiService().getPreferencesData(userId)
.subscribeOn(getSchedulerProvider().getWorkerScheduler())
.observeOn(getSchedulerProvider().getUiScheduler())
.subscribe(
jsonObject -> {
//Log.d(getClass().getSimpleName(), "PreferencesPresenter : loadPreferenceData: onSuccess");
if (!isViewAttached()) {
return;
}
getMvpView().hideLoading();
if (jsonObject != null && AppUtils.containsValue(jsonObject, JsonKeys.DATA))
setupFieldList(new Gson().fromJson(jsonObject.get(JsonKeys.DATA), Preferences.class));
}
, throwable -> {
// Log.d(getClass().getSimpleName(), "PreferencesPresenter : loadPreferenceData: Error");
if (!isViewAttached()) {
return;
}
getMvpView().hideLoading();
handleApiError(throwable);
}));
}
Are you running unit tests? By default, any calls to the Android framework from within a unit test will throw an exception.
From https://developer.android.com/training/testing/fundamentals#interact-android-environment:
You can control and verify the elements of the Android framework with which your app interacts by running unit tests against a modified version of android.jar, which doesn't contain any code. Because your app's calls to the Android framework throw exceptions by default, you need to stub out every one of these interactions by using a mocking framework, such as Mockito.
You have a few options here:
Encapsulate the code that interacts with SharedPreferences in a separate class and mock out that class with Mockito when testing your Presenter
Use Robolectric, which provides an implementation of the Android SDK
Run instrumented tests (androidTest) instead of unit tests (test)
I would recommend #1, as that approach allows you to continue running fast JUnit tests without having to deal with the overhead of Robolectric.

android retrofit2, dagger2 unit test

I learn how to test the presenter layer of MVP architecture in android, my presenter using retrofit 2 and in my activity I used dagger 2 as dependency injection to my presenter, this is my Dagger and presenter injection looks like:
#Inject
AddScreenPresenter addScreenPresenter;
This is the Dagger builder :
DaggerAddScreenComponent.builder()
.netComponent(((App) getApplicationContext()).getNetComponent())
.addScreenModule(new AddScreenModule(this, new ContactDatabaseHelper(this)))
.build().inject(this);
and this is my presenter constructor :
#Inject
public AddScreenPresenter(Retrofit retrofit, AddScreenContact.View view, ContactDatabaseHelper contactDatabaseHelper)
{
this.retrofit = retrofit;
this.view = view;
this.contactDatabaseHelper = contactDatabaseHelper;
}
I have write the unit test class and mock the Retrofit class, but when I run it, the error appears :
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
This is the test class :
#RunWith(MockitoJUnitRunner.class)
public class AddScreenPresenterTest {
private AddScreenPresenter mAddPresenter;
#Mock
private Retrofit mRetrofit;
#Mock
private Context mContext;
#Mock
private AddScreenContact.View mView;
#Mock
private ContactDatabaseHelper mContactDatabaseHelper;
String firstName, phoneNumber;
Upload upload;
#Before
public void setup() {
mAddPresenter = new AddScreenPresenter(mRetrofit, mView, mContactDatabaseHelper);
firstName = "aFirstName";
phoneNumber = "998012341234";
Uri path = Uri.parse("android.resource://"+BuildConfig.APPLICATION_ID+"/" + R.drawable.missing);
upload = new Upload();
upload.title = firstName;
upload.description = "aDescription";
upload.albumId = "XXXXX";
upload.image = new File(path.getPath());
}
#Test
public void checkValidationTest() {
verify(mAddPresenter).checkValidation(firstName, phoneNumber);
}
#Test
public void uploadMultiPartTest() {
verify(mAddPresenter).uploadMultiPart(upload);
}
}
this is my module :
#Module
public class AddScreenModule {
private final AddScreenContact.View mView;
private final ContactDatabaseHelper mContactDatabaseHelper;
public AddScreenModule (AddScreenContact.View view, ContactDatabaseHelper contactDatabaseHelper)
{
this.mView = view;
this.mContactDatabaseHelper = contactDatabaseHelper;
}
#Provides
#CustomScope
AddScreenContact.View providesAddScreenContactView() {
return mView;
}
#Provides
#CustomScope
ContactDatabaseHelper providesContactDatabaseHelper() {
return mContactDatabaseHelper;
}
}
I know that Retrofit class is a final class, and now I stuck and don't know how to create the presenter object in my test class. Please help me, how to create the object of the presenter class with retrofit in the constructor. Feel free to ask if my question is not clear enough, and thank you very much for your help.
Personally I'd make the presenter not depend on the Retrofit class but rather on the services created by Retrofit - These are mockable.
It's hard to say from the code you posted which services your presenter actually uses, but for the sake of simplicity let's say it uses only one and let's say it's AddsService - This is an interface ready to work with Retrofit. Something like this for example
public interface AddsService {
#GET(...)
Call<List<Adds>> getAllAdds();
}
Now you can make your presenter depend on this rather than Retrofit
#Inject
public AddScreenPresenter(AddsService addsService,
AddScreenContact.View view,
ContactDatabaseHelper contactDatabaseHelper){
this.addsService = addsService;
this.view = view;
this.contactDatabaseHelper = contactDatabaseHelper;
}
You now need to provide this dependency. I'm guessing you have also a NetModule since you have a NetComponent, so I assume you can just do:
#Module
public class NetModule {
// Methods providing Retrofit
#Provides
#Singleton
public AddsService providesAddsService(Retrofit retrofit) {
return retrofit.create(AddsService.class);
}
}
Notice how the providesAddsService depends on retrofit? This should be already provided since your presenter is depending on it. You shouldn't need to change anything for that. Dagger is able to figure out how to provide Retrofit to the method providesAddsService.
Please notice also that I'm assuming you can provide these in a Singleton scope. I assume this because in your code you retrieve the component from the application, which should handle the singleton scope.
Now in your tests you can simply mock AddsService and test your presenter.
If your presenter depends on more services, I'd also pass them in the constructor and provide the implementations with Dagger.
As a bonus, let me also say that the retrofit instance and the retrofit services should only be created once (or at least as less times as possible). This is because they're usually expensive operations and you usually always query the same endpoints with different parameters.
EDIT
To answer some of the questions in the comments. First the easy one: How to create the presenter in the test classes? Like you I too try to get away from Dagger during tests, that's why I prefer constructor dependency injection just like you show you're using. So in my test class I'd have something very similar like you:
#RunWith(MockitoJUnitRunner.class)
public class AddScreenPresenterTest {
private AddScreenPresenter mAddPresenter;
#Mock
private AddsService addsService;
// ...
#Before
public void setUp() throws Exception {
mAddPresenter = new AddScreenPresenter(addsService,
mView, mContactDatabaseHelper);
// ...
}
}
So basically the only difference is that I would pass the mock to the service.
Now the second question: How to call the presenter constructor from the activity? Well you don't... that's the whole idea of dependency injection. You should use dagger to provide your presenter. I think this is already what you do and I guess this is what it's in your activity:
#Inject
AddScreenPresenter addScreenPresenter;
So all you need to do is have a provider method in your module that provides this and is able to inject it.
You can also make the component return the presenter provided by the module:
#Component(...)
public interface AddScreenComponent {
AddScreenPresenter getPresenter();
}
And then in your activity you'd do something like:
addScreenPresenter = component.getPresenter();
I don't really have any preference here. The key point is to understand that you should not build the objects yourself (unless inside #Modules). As a rule of thumb any time you see new being used that means you have a tight dependency on that object and you should extract it to be injected. So this is why you should avoid creating the presenter inside your activity. It will couple the presenter to the activity.

Mockito and callback returning "Argument(s) are different!"

I'm trying to use mockito on android. I want to use it with some callback.
Here my test :
public class LoginPresenterTest {
private User mUser = new User();
#Mock
private UsersRepository mUsersRepository;
#Mock
private LoginContract.View mLoginView;
/**
* {#link ArgumentCaptor} is a powerful Mockito API to capture argument values and use them to
* perform further actions or assertions on them.
*/
#Captor
private ArgumentCaptor<LoginUserCallback> mLoadLoginUserCallbackCaptor;
private LoginPresenter mLoginPresenter;
#Before
public void setupNotesPresenter() {
// Mockito has a very convenient way to inject mocks by using the #Mock annotation. To
// inject the mocks in the test the initMocks method needs to be called.
MockitoAnnotations.initMocks(this);
// Get a reference to the class under test
mLoginPresenter = new LoginPresenter(mUsersRepository, mLoginView);
// fixtures
mUser.setFirstName("Von");
mUser.setLastName("Miller");
mUser.setUsername("von.miller#broncos.us");
mUser.setPassword("Broncos50superBowlWinners");
}
#Test
public void onLoginFail_ShowFail() {
// When try to login
mLoginPresenter.login("von.miller#broncos.us", "notGoodPassword");
// Callback is captured and invoked with stubbed user
verify(mUsersRepository).login(eq(new User()), mLoadLoginUserCallbackCaptor.capture());
mLoadLoginUserCallbackCaptor.getValue().onLoginComplete(eq(mUser));
// The login progress is show
verify(mLoginView).showLoginFailed(anyString());
}
But I got this error :
Argument(s) are different! Wanted:
mUsersRepository.login(
ch.example.project.Model.User#a45f686,
<Capturing argument>
);
-> at example.ch.project.Login.LoginPresenterTest.onLoginFail_ShowFail(LoginPresenterTest.java:94)
Actual invocation has different arguments:
mUsersRepository.login(
ch.example.project.Model.User#773bdcae,
ch.example.project.Login.LoginPresenter$1#1844b009
);
Maybe the issue is that the second actual argument is ch.example.project.Login.LoginPresenter$1#1844b009 ?
I followed : https://codelabs.developers.google.com/codelabs/android-testing/#5
Thank you for help =)
Edit
The method I try to test (LoginPresenter):
#Override
public void login(String email, String password) {
mLoginView.showLoginInProgress();
User user = new User();
user.setUsername(email);
user.setPassword(password);
mUsersRepository.login(user, new UsersRepository.LoginUserCallback() {
#Override
public void onLoginComplete(User loggedUser) {
mLoginView.showLoginComplete();
}
#Override
public void onErrorAtAttempt(String message) {
mLoginView.showLoginFailed(message);
}
});
}
eq(new User())
When using eq (or not using matchers at all), Mockito compares arguments using the equals method of the instance passed in. Unless you've defined a flexible equals implementation for your User object, this is very likely to fail.
Consider using isA(User.class), which will simply verify that the object instanceof User, or any() or anyObject() to skip matching the first parameter entirely.
I am using mvp pattern with rxjava 2 and dagger 2, and was stuck on unit testing a presenter using Mockito. The code that gave me the "Argument(s) are different!” Error:
#Mock
ImageService imageService;
#Mock
MetadataResponse metadataResponse;
private String imageId = "123456789";
#Test
public void getImageMetadata() {
when(imageService.getImageMetadata(imageId)).thenReturn(Observable.just(Response.success(metadataResponse)));
presenter.getImageMetaData(imageId);
verify(view).showImageData(new ImageData()));
}
Which throws error messages such as the following:
Argument(s) are different! Wanted: Actual invocation has different
arguments: com.example.model.ImageData#5q3v861
Thanks to the answer from #Jeff Bowman, it worked after I changed this line
verify(view).showImageData(new ImageData()));
with
verify(view).showImageData(isA(ImageData.class));

Get context of test project in Android junit test case

Does anyone know how can you get the context of the Test project in Android junit test case (extends AndroidTestCase).
Note: The test is NOT instrumentation test.
Note 2: I need the context of the test project, not the context of the actual application that is tested.
I need this to load some files from assets from the test project.
There's new approach with Android Testing Support Library (currently androidx.test:runner:1.1.1). Kotlin updated example:
class ExampleInstrumentedTest {
lateinit var instrumentationContext: Context
#Before
fun setup() {
instrumentationContext = InstrumentationRegistry.getInstrumentation().context
}
#Test
fun someTest() {
TODO()
}
}
If you want also app context run:
InstrumentationRegistry.getInstrumentation().targetContext
Full running example: https://github.com/fada21/AndroidTestContextExample
Look here: What's the difference between getTargetContext() and getContext (on InstrumentationRegistry)?
After some research the only working solution seems to be the one yorkw pointed out already. You'd have to extend InstrumentationTestCase and then you can access your test application's context using getInstrumentation().getContext() - here is a brief code snippet using the above suggestions:
public class PrintoutPullParserTest extends InstrumentationTestCase {
public void testParsing() throws Exception {
PrintoutPullParser parser = new PrintoutPullParser();
parser.parse(getInstrumentation().getContext().getResources().getXml(R.xml.printer_configuration));
}
}
As you can read in the AndroidTestCase source code, the getTestContext() method is hidden.
/**
* #hide
*/
public Context getTestContext() {
return mTestContext;
}
You can bypass the #hide annotation using reflection.
Just add the following method in your AndroidTestCase :
/**
* #return The {#link Context} of the test project.
*/
private Context getTestContext()
{
try
{
Method getTestContext = ServiceTestCase.class.getMethod("getTestContext");
return (Context) getTestContext.invoke(this);
}
catch (final Exception exception)
{
exception.printStackTrace();
return null;
}
}
Then call getTestContext() any time you want. :)
If you want to get the context with Kotlin and Mockito, you can do it in the following way:
val context = mock(Context::class.java)
import androidx.test.core.app.ApplicationProvider;
private Context context = ApplicationProvider.getApplicationContext();
#RunWith(AndroidJUnit4.class) let you use Android Context
/**
* Instrumented test, which will execute on an Android device.
*
* #see Testing documentation
*/
#RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
#Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.android.systemui", appContext.getPackageName());
}
}
You can even run it on main thread using runOnMainSync. Here is the complete solution:
#RunWith(AndroidJUnit4::class)
class AwesomeViewModelTest {
#Test
fun testHandler() {
getInstrumentation().runOnMainSync(Runnable {
val context = InstrumentationRegistry.getInstrumentation().targetContext
// Here you can call methods which have Handler
})
}
}
Update: AndroidTestCase This class was deprecated in API level 24.
Use InstrumentationRegistry instead. New tests should be written using the Android Testing Support Library. Link to announcement
You should extend from AndroidTestCase instead of TestCase.
AndroidTestCase Class Overview
Extend this if you need to access Resources or other things that depend on Activity Context.
AndroidTestCase - Android Developers
This is to correct way to get the Context. Other methods are already deprecated
import androidx.test.platform.app.InstrumentationRegistry
InstrumentationRegistry.getInstrumentation().context
The other answers are outdated. Right now every time that you extend AndroidTestCase, there is mContext Context object that you can use.
For those encountering these problems while creating automated tests, you've gotta do this :
Context instrumentationContext;
#Before
public void method() {
instrumentationContext = InstrumentationRegistry.getInstrumentation().getContext();
MultiDex.install(instrumentationContext);
}
Add Mocito Library
testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:core:1.4.0'
testImplementation 'org.mockito:mockito-core:3.10.0'
Add Annoatation call #Mock where ever need for example for Context
#RunWith(MockitoJUnitRunner::class)
class EmailValidatorTest {
#Mock
private lateinit var context: Context
lateinit var utils:Utils
#Before
fun launch()
{
utils=Utils(context)
}
#Test
fun emailValidator_NullEmail_ReturnsFalse() {
assertFalse(utils.isValidEmail(null))
}
}
For Kotlin unit test with #RunWith(AndroidJUnit4::class)
Add this dependency for kotlin test
implementation 'androidx.test:core-ktx:1.5.0'
In the test class access context using the below snippet.
private val context = ApplicationProvider.getApplicationContext<Context>()

Categories

Resources