I have written 2 test cases which will test 2 functions of a class which does similar kind of functiomality.
This is my PrefHelper class in project:
public String getID() {
if(ID==null) {
ID = sharedpreferences.getString(_ID, null);
}
return ID ;
}
public void setID(String ID ) {
prefsEditor.putString(_ID,ID );
prefsEditor.apply();
this.ID = ID ;
}
public String getApiBasePath() {
if(apiBasePath==null) {
apiBasePath = sharedpreferences.getString(API_BASE_PATH, null);
}
return apiBasePath;
}
public void setApiBasePath(String apiBasePath) {
prefsEditor.putString(API_BASE_PATH,apiBasePath);
prefsEditor.apply();
this.apiBasePath = apiBasePath;
}
And following is my test class:
#Mock
Context context;
#Mock
SharedPreferences sharedPreferences;
#Mock
SharedPreferences.Editor editor;
private PrefsHelper prefsHelper;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(context.getSharedPreferences(anyString(),anyInt())).thenReturn(sharedPreferences);
when(sharedPreferences.edit()).thenReturn(editor);
when(sharedPreferences.getString(anyString(),isNull(String.class))).thenReturn(null);
when(editor.putString(anyString(),anyString())).thenReturn(editor);
prefsHelper = PrefsHelper.getInstance(context);
}
#Test
public void apiBasePathTest(){
String returnNull=prefsHelper.getApiBasePath();
verify(sharedPreferences,times(1)).getString(anyString(),isNull(String.class));
assertEquals(null,returnNull);
doNothing().when(editor).apply();
final ArgumentCaptor<String> stringsCaptor =
ArgumentCaptor.forClass(String.class);
prefsHelper.setApiBasePath("basePath");
verify(editor,times(1)).putString(anyString(),stringsCaptor.capture());
verify(editor,times(1)).apply();
assertEquals("basePath", stringsCaptor.getAllValues().get(0));
String returnNonNull=prefsHelper.getApiBasePath();
verify(sharedPreferences,times(1)).getString(anyString(),isNull(String.class));
assertEquals("basePath",returnNonNull);
}
#Test
public void IDTest(){
String returnNull=prefsHelper.getID();
assertEquals(null,returnNull);
verify(sharedPreferences,times(1)).getString(anyString(),isNull(String.class));
doNothing().when(editor).apply();
final ArgumentCaptor<String> stringsCaptor =
ArgumentCaptor.forClass(String.class);
prefsHelper.setID("ID");
verify(editor,times(1)).putString(anyString(),stringsCaptor.capture());
verify(editor,times(1)).apply();
assertEquals("ID", stringsCaptor.getAllValues().get(0));
String returnNonNull=prefsHelper.getID();
verify(sharedPreferences,times(1)).getString(anyString(),isNull(String.class));
assertEquals("ID",returnNonNull);
}
First test case runs fine, it fails in second test at all the verify method. Its assert true works fine but verify fails in all cases and I get the below error:
Wanted but not invoked:
sharedPreferences.getString(<any>, isNull());
-> at PrefsHelperTest.IDTest
Actually, there were zero interactions with this mock. I am surprised that it works fine on first test but fails on second, where both test are doing similar functionality.
Related
the class i want to test is posted below in the code section. I am trying to test the "setSubscriberName" method.
the test I coded is posted below in the testing section. but at run time the test fails
please let me know how to test that setter method correctly
code
public class ListViewModel {
private String mSubscriberName = null;
public ListViewModel(String subscriberName) {
mSubscriberName = subscriberName;
}
public void setSubscriberName(String name) {
mSubscriberName = name;
}
}
testing:
public class ListViewModelTest {
#Mock
private ListViewModel mListViewModel = null;
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Before
public void setUp() throws Exception {
mListViewModel = new ListViewModel("");
}
public void setSubscriberName(String str) {
String mSubscriberName = null;
mSubscriberName = str;
}
#Test
public void setSubscriberNameTest() throws Exception {
ListViewModel spyListView = spy(mListViewModel);
verify(spyListView).setSubscriberName("abc");
}
}
I am trying to test the text of the button is changing while trying to login when the button is pressed. Login button text is actually changing the text to Verifying... when the system is checking the credentials with server and once done, the text changes to LOGIN again. Whenever I am trying to test that with espresso;the UI part is completing assigning values to the edittext and clicked the button, the thread then freezes and few times later it throws an error. As I am new in testing, I would be grateful if you could explain how this problem can be resolved or what is the approach that I should take for this kind of scenarios.
This is my test class.
#LargeTest
#RunWith(AndroidJUnit4.class)
public class LoginTest {
private String userName;
private String userPass;
private LoginIdlingResource idlingResource;
#Rule
public ActivityTestRule<LoginActivity> activityRule = new
ActivityTestRule<LoginActivity>(LoginActivity.class);
#Before
public void assignCredentials (){
userName = "ABC";
userPass = "ABC";
}
#Before
public void registerIntentServiceIdlingResource() throws Exception {
LoginActivity activity = activityRule.getActivity();
idlingResource = new LoginIdlingResource(activity);
Espresso.registerIdlingResources(idlingResource);
}
#Test
public void buttonTextChanged(){
onView(withId(R.id.edittext_user))
.perform(typeText(userName));
onView(withId(R.id.edittext_pass))
.perform(typeText(userPass));
onView(withId(R.id.submit_login))
.perform(click());
onView(withId(R.id.submit_login))
.check(matches(withText("Verifying...")));
}
#After
public void unregisterIntentServiceIdlingResource() {
Espresso.unregisterIdlingResources(idlingResource);
}
}
I have prepared an IdlingResource.
public class LoginIdlingResource implements IdlingResource {
private LoginActivity mActivity;
private ResourceCallback mCallBack;
public LoginIdlingResource(LoginActivity context){
mActivity = context;
}
#Override
public String getName() {
return "LoginActivityIdleName";
}
#Override
public boolean isIdleNow() {
boolean idle = isIdle();
if (idle && mCallBack!=null){
mCallBack.onTransitionToIdle();
}
return idle;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.mCallBack = callback;
}
public boolean isIdle() {
return mActivity != null && mCallBack != null &&
!mActivity.isNetworkOperationGoingOn();
}
}
This is generating the following exception
android.support.test.espresso.PerformException: Error performing 'single
click - At Coordinates: 539, 903 and precision: 16, 16' on view 'with id:
com.abc.rt:id/submit_login'.
at android.support.test.espresso.PerformException$Builder.
build(PerformException.java:83)
at android.support.test.espresso.base.DefaultFailureHandler.
getUserFriendlyError(DefaultFailureHandler.java:80)
at android.support.test.espresso.base.DefaultFailureHandler.
handle(DefaultFailureHandler.java:56)
at
android.support.test.espresso.ViewInteraction.
runSynchronouslyOnUiThread(ViewInteraction.java:184)
at android.support.test.espresso.ViewInteraction.
doPerform(ViewInteraction.java:115)
at android.support.test.espresso.ViewInteraction.
perform(ViewInteraction.java:87)
at com.abc.rt.LoginTest.buttonTextChanged(LoginTest.java:92)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at org.junit.runners.model.FrameworkMethod$1.
runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.
run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.
invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.
evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.
evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.
evaluate(RunAfters.java:27)
at android.support.test.internal.statement.UiThreadStatement.
evaluate(UiThreadStatement.java:55)
at android.support.test.rule.ActivityTestRule$ActivityStatement.
evaluate(ActivityTestRule.java:270)
Try registering your IdlingResource after you click the button:
// remove #Before
#Test
public void buttonTextChanged(){
onView(withId(R.id.edittext_user))
.perform(typeText(userName));
onView(withId(R.id.edittext_pass))
.perform(typeText(userPass));
onView(withId(R.id.submit_login))
.perform(click());
LoginIdlingResource idlingResource = new LoginIdlingResource(activityRule.getActivity());
Espresso.registerIdlingResources(idlingResource);
onView(withId(R.id.submit_login))
.check(matches(withText("Verifying...")));
Espresso.unregisterIdlingResources(idlingResource);
}
// remove #After
This could be because of the soft keyboard and because there are fields hidden behind it. You can refer to this answer:
https://stackoverflow.com/a/51449748/6248208
Looking at this example: https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample
I've implemented the same pattern in one of my side projects however, I'm facing difficulties getting tests to work as expected.
I'm trying to test my one of my repository classes. The test checks if the repository fetches data from the api and if the value of the observer changes.
Here is the test class
#RunWith(JUnit4.class)
public class TimelineRepositoryTest {
private SharedPreferences sharedPreferences;
private DatabaseDao databaseDao;
private ApiService apiService;
private TimelineRepository timelineRepository;
#Rule
public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
#Before
public void setup() {
sharedPreferences = mock(SharedPreferences.class);
databaseDao = mock(DatabaseDao.class);
apiService = mock(ApiService.class);
timelineRepository = new TimelineRepository(apiService, sharedPreferences, databaseDao);
timelineRepository.appExecutors = new InstantAppExecutors();
}
#Test
public void fetchTimelineWithForceFetch() {
TimelineResponse timelineResponse = new TimelineResponse();
when(sharedPreferences.getLong(PreferenceUtils.PREFERENCE_LAST_TIMELINE_REFRESH, 0)).thenReturn(0L);
when(apiService.retrieveTimeline()).thenReturn(ApiUtil.successCall(timelineResponse));
MutableLiveData<List<Event>> dbData = new MutableLiveData<>();
when(databaseDao.loadEvents()).thenReturn(dbData);
Observer observer = mock(Observer.class);
timelineRepository.getTimelineEvents().observeForever(observer);
verify(observer).onChanged(Resource.loading(null));
verify(observer).onChanged(Resource.success(new ArrayList<Event>());
}
}
Also, here is the actual repository class:
public class TimelineRepository {
#Inject AppExecutors appExecutors;
#Inject #Named("timelineRefreshDurationInMillis") long timelineRefreshDurationInMillis;
private final DatabaseDao databaseDao;
private final SharedPreferences sharedPreferences;
private final ApiService apiService;
public TimelineRepository(ApiService apiService, SharedPreferences sharedPreferences, DatabaseDao databaseDao) {
this.apiService = apiService;
this.sharedPreferences = sharedPreferences;
this.databaseDao = databaseDao;
}
public LiveData<Resource<List<Event>>> getTimelineEvents() {
return new NetworkBoundResource<List<Event>, TimelineResponse>(appExecutors) {
#Override
protected void saveCallResult(#NonNull TimelineResponse timelineResponse) {
if (timelineResponse.events != null) {
databaseDao.saveEvents(timelineResponse.events);
}
PreferenceUtils.storeLastTimelineRefreshTimeInMillis(sharedPreferences, System.currentTimeMillis());
}
#Override
protected boolean shouldFetch(#Nullable List<Event> data) {
return System.currentTimeMillis() - PreferenceUtils.getLastTimelineRefreshTimeInMillis(sharedPreferences) > timelineRefreshDurationInMillis;
}
#NonNull
#Override
protected LiveData<List<Event>> loadFromDb() {
return databaseDao.loadEvents();
}
#NonNull
#Override
protected LiveData<ApiResponse<TimelineResponse>> createCall() {
return apiService.retrieveTimeline();
}
}.getAsLiveData();
}
}
I want to use the test to check if the mocked observer is called multiple times with different values. However, the test says that it is only called one with the loading argument.
After some debugging it seems like the NetworkBoundResource's https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/NetworkBoundResource.java#L48 observer registered in the constructor is not called.
Has anyone faced this issue?
Can someone please show me a working unit test om this code, using mockito? Im new to testing in Android studio and could really need some help.
public class PreferenceHelper {
public static final String SHARED_PREFS_NAME = "EDUBACK_PREFS";
public static final String PREF_KEY_IS_STUDENT = "PREF_KEY_IS_STUDENT";
private final SharedPreferences mPref;
public PreferenceHelper(Context context) {
mPref = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
}
public void setIsStudent(boolean isStudent) {
mPref.edit().putBoolean(PREF_KEY_IS_STUDENT, isStudent).apply();
}
public boolean getIsStudent() {
return mPref.getBoolean(PREF_KEY_IS_STUDENT, true); // Default true
}}
Actually it is not necessary to use mockito here.
You can test your class something like that (for assertions I use org.assertj:assertj-core:3.5.2 library):
#RunWith(RobolectricTestRunner.class)
public class PreferenceHelperTest {
private SharedPreferences sharedPreferences;
private PreferenceHelper preferenceHelper;
#Before
public void setUp() {
sharedPreferences = RuntimeEnvironment.application.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
preferenceHelper = new PreferenceHelper(ShadowApplication.getInstance().getApplicationContext());
}
#Test
public void setIsStudent_whenIsStudentIsTrue() {
preferenceHelper.setIsStudent(true);
assertThat(sharedPreferences.getBoolean(PREF_KEY_IS_STUDENT, false)).isTrue();
}
#Test
public void setIsStudent_whenIsStudentIsFalse() {
preferenceHelper.setIsStudent(false);
assertThat(sharedPreferences.getBoolean(PREF_KEY_IS_STUDENT, true)).isFalse();
}
#Test
public void getIsStudent_whenIsStudentIsNull() {
boolean getIsStudent = preferenceHelper.getIsStudent();
assertThat(getIsStudent).isTrue();
}
#Test
public void getIsStudent_whenIsStudentIsFalse() {
preferenceHelper.setIsStudent(false);
boolean getIsStudent = preferenceHelper.getIsStudent();
assertThat(getIsStudent).isFalse();
}
#Test
public void getIsStudent_whenIsStudentIsTrue() {
preferenceHelper.setIsStudent(true);
boolean getIsStudent = preferenceHelper.getIsStudent();
assertThat(getIsStudent).isTrue();
}
}
I've up until yesterday successfully put together a very readable Android project using the MVP-pattern and the Android Annotations library.
But yesterday when I started writing unittest for my LoginPresenter a problem has shown itself.
First some code from my LoginPresenter.
...
#EBean
public class LoginPresenterImpl implements LoginPresenter, LoginInteractor.OnLoginFinishedListener {
#RootContext
protected LoginActivity loginView;
#Bean(LoginInteractorImpl.class)
LoginInteractor loginInteractor;
#Override public void validateCredentials(String username, String password) {
if (loginView != null) {
loginView.showProgress();
}
if (TextUtils.isEmpty(username)) {
// Check that username isn't empty
onUsernameError();
}
if (TextUtils.isEmpty(password)){
// Check that password isn't empty
onPasswordError();
// No reason to continue to do login
} else {
}
}
#UiThread(propagation = UiThread.Propagation.REUSE)
#Override public void onUsernameError() {
if (loginView != null) {
loginView.setUsernameError();
loginView.hideProgress();
}
}
...
My test:
#RunWith(MockitoJUnitRunner.class)
public class LoginPresenterImplTest {
private LoginPresenter loginPresenter;
#Mock
private LoginPresenter.View loginView;
#Before
public void setUp() {
// mock or create a Context object
Context context = new MockContext();
loginPresenter = LoginPresenterImpl_.getInstance_(context);
MockitoAnnotations.initMocks(this);
}
#After
public void tearDown() throws Exception {
loginPresenter = null;
}
#Test
public void whenUserNameIsEmptyShowUsernameError() throws Exception {
loginPresenter.validateCredentials("", "testtest");
// verify(loginPresenter).onUsernameError();
verify(loginView).setUsernameError();
}
}
The problem is I've not used the standard approach of using MVP-pattern but instead trying out Android Annotations to make the code more readable. So I've not used attachView()- or detachView()-methods for attaching my presenter to my LoginActivity (view). This means that I can't mock my "view". Does someone know a workaround for this problem. I keep getting following message when running the test:
Wanted but not invoked:
loginView.setUsernameError();
-> at com.conhea.smartgfr.login.LoginPresenterImplTest.whenUserNameIsEmptyShowUsernameError(LoginPresenterImplTest.java:48)
Actually, there were zero interactions with this mock.
Solution (I'm not using #RootContext anymore):
Presenter:
#EBean
public class LoginPresenterImpl extends AbstractPresenter<LoginPresenter.View>
implements LoginPresenter, LoginInteractor.OnLoginFinishedListener {
private static final String TAG = LoginPresenterImpl.class.getSimpleName();
#StringRes(R.string.activity_login_authenticating)
String mAuthenticatingString;
#StringRes(R.string.activity_login_aborting)
String mAbortingString;
#StringRes(R.string.activity_login_invalid_login)
String mInvalidCredentialsString;
#StringRes(R.string.activity_login_aborted)
String mAbortedString;
#Inject
LoginInteractor mLoginInteractor;
#Override
protected void initializeDagger() {
Log.d(TAG, "Initializing Dagger injection");
Log.d(TAG, "Application is :" + getApp().getClass().getSimpleName());
Log.d(TAG, "Component is: " + getApp().getComponent().getClass().getSimpleName());
Log.d(TAG, "UserRepo is: " + getApp().getComponent().userRepository().toString());
mLoginInteractor = getApp().getComponent().loginInteractor();
Log.d(TAG, "LoginInteractor is: " + mLoginInteractor.getClass().getSimpleName());
}
#Override
public void validateCredentials(String username, String password) {
boolean error = false;
if (!isConnected()) {
noNetworkFailure();
error = true;
}
if (TextUtils.isEmpty(username.trim())) {
// Check that username isn't empty
onUsernameError();
error = true;
}
if (TextUtils.isEmpty(password.trim())) {
// Check that password isn't empty
onPasswordError();
error = true;
}
if (!error) {
getView().showProgress(mAuthenticatingString);
mLoginInteractor.login(username, password, this);
}
}
...
My tests (some of them):
#RunWith(AppRobolectricRunner.class)
#Config(constants = BuildConfig.class)
public class LoginPresenterImplTest {
#Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
private LoginPresenterImpl_ mLoginPresenter;
#Mock
private LoginPresenter.View mLoginViewMock;
#Mock
private LoginInteractor mLoginInteractorMock;
#Captor
private ArgumentCaptor<LoginInteractor.OnLoginFinishedListener> mCaptor;
#Before
public void setUp() {
mLoginPresenter = LoginPresenterImpl_.getInstance_(RuntimeEnvironment.application);
mLoginPresenter.attachView(mLoginViewMock);
mLoginPresenter.mLoginInteractor = mLoginInteractorMock;
}
#After
public void tearDown() throws Exception {
mLoginPresenter.detachView();
mLoginPresenter = null;
}
#Test
public void whenUsernameAndPasswordIsValid_shouldLogin() throws Exception {
String authToken = "Success";
mLoginPresenter.validateCredentials("test", "testtest");
verify(mLoginInteractorMock, times(1)).login(
anyString(),
anyString(),
mCaptor.capture());
mCaptor.getValue().onSuccess(authToken);
verify(mLoginViewMock, times(1)).loginSuccess(authToken);
verify(mLoginViewMock, times(1)).hideProgress();
}
#Test
public void whenUsernameIsEmpty_shouldShowUsernameError() throws Exception {
mLoginPresenter.validateCredentials("", "testtest");
verify(mLoginViewMock, times(1)).setUsernameError();
verify(mLoginViewMock, never()).setPasswordError();
verify(mLoginViewMock, never()).hideProgress();
}
...
As a workaround you can have this:
public class LoginPresenterImpl ... {
...
#VisibleForTesting
public void setLoginPresenter(LoginPresenter.View loginView) {
this.loginView = loginView;
}
}
In test class:
#Before
public void setUp() {
...
MockitoAnnotations.initMocks(this);
loginPresenter.setLoginPresenter(loginView);
}
But, as a rule of thumb, when you see #VisibleForTesting annotation, that means you have ill architecture. Better to refactor your project.
Heads up to Developers that want to use Android Annotations in their project. Watch out when writing unittests that your code doesn't access the Android APIs. The underlying implementation of Android Annotations is heavily dependent on the Android APIs. So the code that is autogenerated could be dependent on this and make it difficult to write unittests.
Always remember that Android Annotations replaces your class with a final class that has an _ added at the end of it's classname. In this generated class a lot of boilerplate code is autogenerated depending on how the original class is annotated. In my case the problem is that I'm working on an Android-project and want a lot of my methods from my presenter to run on the UI-thread. This is achieved using Android Annotations using the #UIThread annotation. But this means that my method is actually wrapped with another method that calls the super-class:
#Override
public void onUsernameError() {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
LoginPresenterImpl_.super.onUsernameError();
return;
}
UiThreadExecutor.runTask("", new Runnable() {
#Override
public void run() {
LoginPresenterImpl_.super.onUsernameError();
}
}
, 0L);
}
My testcase can't get past the line:
...
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
...
And that is of course because we don't have access to the Android APIs in a simple unittest. So in there lies the problem.
Conclusion: You have to be very careful when writing unittests for projects using Android Annotations, that the code that is autogenerated doesn't rely on Android related APIs.
It's the same problem when using androids TextUtil-class.