Hot questions for Using Mockito in android mvp

Question:

I'm trying to unit test my Presenter class using Mockito and I always end up failing the test:

org.mockito.exceptions.base.MockitoException: 
Cannot mock/spy class java.lang.String
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types

This is what my Presenter class looks like:

public class EditorPresenter implements EditorContract.Presenter {

    private DataSource dataSourceImpl;
    private EditorContract.View mView;
    private SharedPreferences prefs;

    EditorPresenter(SharedPreferences prefs,
                    DataSourceImpl dataSourceImpl,
                    EditorContract.View mView) {
        this.dataSourceImpl = dataSourceImpl;
        this.mView = mView;
        this.prefs = prefs;

        mView.setPresenter(this);
    }

    @Override
    public void showNewNote() {
        String noteColor = prefs.getString("default_note_color", "#ef5350");
        String textColor = prefs.getString("default_text_color", "#000000");
        mView.noteColor(Color.parseColor(noteColor));
        mView.textColor(Color.parseColor(textColor));
     }
}

And this is what I've done so far in EditorPresenterTest class:

public class EditorPresenterTest {
    @Mock
    private EditorContract.View mView;
    @Mock
    private DataSourceImpl mDataSourceImpl;
    @Mock
    private SharedPreferences sharedPrefs;
    @Mock
    private String noteColor;
    @Mock
    private String textColor;

    @Before
    public void setUpEditorPresenter() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void createEditorPresenter_newNote() {
        EditorPresenter editorPresenter = new EditorPresenter(
                sharedPrefs,
                mDataSourceImpl,
                mView);
         verify(mView).setPresenter(editorPresenter);
    }
    @Test
    public void showNewNote() {
        when(sharedPrefs.getString(eq("default_note_color"), eq("#ef5350"))).thenReturn(noteColor);
        when(sharedPrefs.getString(eq("default_text_color"), eq("#000000"))).thenReturn(textColor);
        verify(mView).textColor(Color.parseColor(noteColor));
        verify(mView).noteColor(Color.parseColor(textColor));
    }

(Note: I'm new to Mockito and testing) I have passed the createEditorPresenter_newNote() but the showNewNote() failed the test and shows error. Any Feedback/Answers are welcome. Hope someone helps me. Thanks!


Answer:

I will first answer the exact question that you asked here but keep in mind that you have a larger issue that is hiding behind your compilation error, for which I will provide an answer right after. (please keep in mind that I have no real experience with Android, so exact class names and use cases might not be valid, but your issues are more with understanding what test frameworks do and not syntax-oriented).

Your first issue is that you are trying to create mock types of the String class, which is final. As you can see in the error from Mockito:

Mockito cannot mock/spy following:

- final classes

In essence, there is no real reason for creating a mock of a String, because you are not testing String functionality. You can just use a constant. if that is what you wish to fix, just remove the @Mock annotations from the noteColor and textColor variables and initialize them with some constant values.


More about testing frameworks and the other problems you are facing:

There is another major issue in your test case, and that is that you are trying to use the EditorPresenter you created in the first test inside the scope of the second test.

The thing is that test frameworks run different tests in separate states (rightfully so). So when you create the EditorPresenter instance inside the createEditorPresenter_newNote method, it won't be visible for you in the showNewNote test method, because it is a different process (not a different CPU process - just a process in the simple day-to-day term of the word).


What should you be doing instead?

That's what the before method is for: it will be called before every test runs, so you can set up shared functionality in one place.

what you should be doing is more on the line of this:

public class EditorPresenterTest {
    @Mock
    private EditorContract.View mView;
    @Mock
    private DataSourceImpl mDataSourceImpl;
    @Mock
    private SharedPreferences sharedPrefs;

    private EditorPresenter editorPresenter;

    @Before
    public void setUpEditorPresenter() {
        MockitoAnnotations.initMocks(this);
        this.editorPresenter = new EditorPresenter(
                sharedPrefs,
                mDataSourceImpl,
                mView);
    }

    @Test
    public void createEditorPresenter_newNote() {
         verify(mView).setPresenter(editorPresenter);
    }

    @Test
    public void showNewNote() {
        editorPresenter.showNewNote();
        String noteColor = "#ef5350"; // or whatever color you want
        String textColor = "#000000"; // or whatever color you want
        when(sharedPrefs.getString(eq("default_note_color"), eq("#ef5350"))).thenReturn(noteColor);
        when(sharedPrefs.getString(eq("default_text_color"), eq("#000000"))).thenReturn(textColor);
        verify(mView).textColor(Color.parseColor(noteColor));
        verify(mView).noteColor(Color.parseColor(textColor));
    }
}

Question:

I'm trying to test my app with Mockito. It is built using MVP pattern. This is my Contract:

public interface CitiesContract {
    interface View {
        void addCitiesToList(List<City> cityList);
    }

    interface Presenter {
        void passCityListToView();
    }

    interface Model {
        List<City> getCityList();
    }
}

This is my View:

public class CitiesActivity extends AppCompatActivity implements CitiesContract.View {
    private List<City> cityList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cities);

        CitiesPresenter presenter = new CitiesPresenter(this);
        presenter.passCityListToView();
    }

    @Override
    public void addCitiesToList(List<City> cities) {
        cityList.addAll(cities);
    }
}

This is my Presenter:

public class CitiesPresenter implements CitiesContract.Presenter {
    private CitiesContract.View view;
    private CitiesModel model;

    public CitiesPresenter(CitiesContract.View view) {
        this.view = view;
        model = new CitiesModel();
    }

    @Override
    public void passCityListToView() {
        List<City> cityList = model.getCityList();
        view.addCitiesToList(cityList);
    }
}

This is my Model:

public class CitiesModel implements CitiesContract.Model {
    @Override
    public List<City> getCityList() {
        List<City> cityList = new ArrayList<>();
        //Add 30 cities to the list
        return cityList;
    }
}

How can I test the passCityListToView() method within my Presenter? This is what I have tried so far:

public class CitiesPresenterTest {
    private CitiesContract.Presenter citiesPresenter;
    @Mock
    private CitiesContract.View citiesView;
    @Mock
    private CitiesContract.Model citiesModel;

    public void setUp() {
        MockitoAnnotations.initMocks(this);
        citiesPresenter = new CitiesPresenter(citiesView);
        citiesModel = new CitiesModel();
    }

    @Test
    public void testCityListIfNull() {
        when(citiesModel.getCityList()).thenReturn(null);
        citiesPresenter.passCityListToView();
        verify(citiesView).addCitiesToList(null);
    }
}

But I get NullPointerException pointing to this line:

when(citiesModel.getCityList()).thenReturn(null);

How can I successfully pass this test? Thanks in advance.

Edit:

As requested, this is my logcat:

java.lang.NullPointerException
    at com.example.CitiesPresenterTest.testCityListIfNull(CitiesPresenterTest.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    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.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Answer:

Multiple things wrong.

First:

@Mock
private CitiesContract.Model citiesModel;

that is basically undone by:

citiesModel = new CitiesModel();

The annotation alone is enough to create a mocked object (to be precise: the annotation together with that initMocks() call).

But you then come in and create a real object, which you then try to add a specification for. But when this here is invoked:

when(citiesModel.getCityList()).thenReturn(null);

as said, citiesModel isn't a Mockito created mock, but a real object of your production class.

That can't work. So, start by removing citiesModel = new CitiesModel(); from your code.

Next:

You do model = new CitiesModel(); in your presenter class. But there is no magic that would somehow put your mocked model instance into that field of the presenter class.

In other words: just declaring a mock alone isn't sufficient. You have to insure that it gets injected into your class under test. Either by passing it using a "test only" constructor (that takes more arguments) or by using the @InjectMocks annotation.

Question:

Hello i try to use mockito, to verify user password if invalid i want to verify it show error message.

But i got this following error :

Wanted but not invoked:
loginView.showPasswordError();
-> at android.fanjavaid.com.tdd_android_login.LoginActivityTest.invalidPassword_notLoggedIn_showPasswordError(LoginActivityTest.java:84)

However, there was exactly 1 interaction with this mock:
loginView.showEmailError();
-> at android.fanjavaid.com.tdd_android_login.presenter.LoginPresenterImpl.validateInput(LoginPresenterImpl.java:23)

Here is my test method :

@Test
public void invalidEmail_notLoggedIn_showEmailError() {
    LoginPresenter presenter = new LoginPresenterImpl(loginView, validator);
    presenter.validateInput(user);

    verify(loginView).showEmailError();
}

@Test
public void invalidPassword_notLoggedIn_showPasswordError() {
    when(user.getEmailAddress()).thenReturn("fanjavaid@gmail.com");

    LoginPresenter presenter = new LoginPresenterImpl(loginView, validator);
    presenter.validateInput(user);

    verify(loginView).showPasswordError();
}

I already mock the email user in invalidPassword_notLoggedIn_showPasswordError() with valid input, but i still get that error message.

Here is my Presenter implementation :

@Override
public void validateInput(User user) {
    if (!validator.validateEmail(user.getEmailAddress())) {
        view.showEmailError();
    } else if (validator.validatePassword(user.getPassword())) {
        view.showPasswordError();
    }
}

What i am missing for?

Thank you


Answer:

✔ ANSWER

After several minutes explore again, i found something interesting. I forgot to add mock to one class. Below i mock some classes moreless like this :

@RunWith(MockitoJUnitRunner.class)
public class LoginActivityTest {

    @Mock User user;
    @Mock LoginView loginView;
    @Mock MyValidator validator;

    LoginPresenter presenter;

    @Before
    public void beforeTest() {
        presenter = new LoginPresenterImpl(loginView, validator);
    }

...

You can see that i mock validator class.

I got the error because in invalidPassword_notLoggedIn_showPasswordError() method i didn't add mock value for email validation.

// Mock validation
when(validator.validateEmail(user.getEmailAddress())).thenReturn(true);

If i don't mock it, it will ask about showEmailError() but we just verify showPasswordError()

This cause my implementation using condition to check one by one, started from check email is valid or not, then password valid or not. If email doesn't exists and return value from validator doesn't exists the error will occured.

So i need to mock email address as valid and mock validator to return true(valid email).

That's my explanation and hope can help anyone who try mockito.

Question:

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();
}
...

Answer:

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.