Proper mocking and testing of interactor class in Android - android

I have a bit problem setting up proper unit tests for my interactor classes in my android app. These classes is where I have "business logic" of my app.
Here is one such class:
public class ChangeUserPasswordInteractor {
private final FirebaseAuthRepositoryType firebaseAuthRepositoryType;
public ChangeUserPasswordInteractor(FirebaseAuthRepositoryType firebaseAuthRepositoryType) {
this.firebaseAuthRepositoryType = firebaseAuthRepositoryType;
}
public Completable changeUserPassword(String newPassword){
return firebaseAuthRepositoryType.getCurrentUser()
.flatMapCompletable(firebaseUser -> {
firebaseAuthRepositoryType.changeUserPassword(firebaseUser, newPassword);
return Completable.complete();
})
.observeOn(AndroidSchedulers.mainThread());
}
}
Here is a test I wrote:
#RunWith(JUnit4.class)
public class ChangeUserPasswordInteractorTest {
#Mock
FirebaseAuthRepositoryType firebaseAuthRepositoryType;
#Mock
FirebaseUser firebaseUser;
#InjectMocks
ChangeUserPasswordInteractor changeUserPasswordInteractor;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
RxAndroidPlugins.reset();
RxAndroidPlugins.setInitMainThreadSchedulerHandler(schedulerCallable -> Schedulers.trampoline());
}
#Test
public void changeUserPassword() {
Mockito.when(firebaseAuthRepositoryType.getCurrentUser()).thenReturn(Observable.just(firebaseUser));
Mockito.when(firebaseAuthRepositoryType.changeUserPassword(firebaseUser, "test123")).thenReturn(Completable.complete());
changeUserPasswordInteractor.changeUserPassword("test123")
.test()
.assertSubscribed()
.assertNoErrors()
.assertComplete();
}
}
Problem here I am having is that this test completes with no errors, even If I change the password from "test123" on changeUserPassword invokation to something else, or if I in the mock return "Completable.onError(new Throwable())".
I can't understand this behavior. Any suggestions how to set up the test?

The last line of your flatMapCompletable always returns Completable.complete()
it should be :
firebaseAuthRepositoryType.changeUserPassword(firebaseUser, newPassword);
so :
public Completable changeUserPassword(String newPassword){
return firebaseAuthRepositoryType.getCurrentUser()
.flatMapCompletable(firebaseUser ->
firebaseAuthRepositoryType.changeUserPassword(firebaseUser, newPassword));
}

Related

How to test function inside RxJava flatMap?

I have the following method in the presenter.
public void addNote(int customerId, String body) {
disposables = RxUtil.initDisposables(disposables);
if (TextUtils.isEmpty(body)) {
view.showNoteTextEmpty();
return;
}
if (customerId == Constants.ZERO) {
view.showNoteError("There is a problem with adding note. Try again!");
return;
}
Disposable disposable = userPrefRepository.getLoggedInUser()
.subscribeOn(Schedulers.io())
.map(user -> getNote(body, user))
.flatMap(note -> customersRepository.addNote(customerId, note))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
if (response.isSuccessful()) {
view.onNoteAdded();
} else if (response.code() == 401) {
view.handleUnauthorisedError();
} else {
view.onNoteNotAdded();
}
}, view::handleError);
disposables.add(disposable);
}
Now I want to unit test it with the following class:
#RunWith(PowerMockRunner.class) #PrepareForTest(TextUtils.class)
public class NoteDetailsPresenterTest extends BaseTest {
#Rule TrampolineSchedulerRule trampolineSchedulerRule = new TrampolineSchedulerRule();
#Mock CustomersRepository customersRepository;
#Mock UserRepository userRepository;
#Mock RolesManager rolesManager;
#Mock NoteDetailsPresenter.View view;
private NoteDetailsPresenter presenter;
#Before
public void setUp() {
mockTextUtils();
presenter = new NoteDetailsPresenter(customersRepository, userRepository, rolesManager);
presenter.setView(view);
}
#Test
public void shouldAddNote() {
// Given
User user = User.newBuilder()
.withUserId(1)
.build();
// When
Mockito.when(userRepository.getLoggedInUser()).thenReturn(Single.just(user));
Note note = presenter.getNote("Note body", user);
Response<Note> response = Response.success(200, note);
Mockito.when(customersRepository.addNote(1, note)).thenReturn(Single.just(response));
presenter.addNote(1, "Note body");
// Then
Mockito.verify(view).onNoteAdded();
}
}
But it fails with the following exception:
Wanted but not invoked:
view.onNoteAdded();
-> at com.anstar.presentation.notes.NoteDetailsPresenterTest.shouldAddNote(NoteDetailsPresenterTest.java:56)
However, there were other interactions with this mock:
view.handleError(
java.lang.NullPointerException: The single returned by the mapper is null
);
-> at io.reactivex.internal.observers.ConsumerSingleObserver.onError(ConsumerSingleObserver.java:46)
How I can solve it? Is is the problem regarding map and flatMap transformations?
It seems that the mock can't be read. Try to put any() on the parameters:
Instead of this:
Mockito.when(customersRepository.addNote(1, note)).thenReturn(Single.just(response));
Use any():
Mockito.when(customersRepository.addNote(anyInt(), any(Note.class))).thenReturn(Single.just(response));
Why the mock can't read?
If parameters are primitive data types (string, int, double, etc) you can just pass the exact parameter (in your case, the first parameter which is integer, 1) and the mock will be read. However in objects (in your case, Note object), even though you have the same exact parameters, they will have different hashCode() so the mock can' t be read. Solution for this is to accept any() parameter specifying the class type: any(Note.class).

Why does Mockito complain about misusing.WrongTypeOfReturnValue when the parameters are correct?

I am running my login unit tests which keep returning errors :
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
ObservableJust cannot be returned by doServerLoginApiCall()
doServerLoginApiCall() should return Single
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
I am not sure why that throws, even when I cross checked everything is correct even though tests keep failing due to the above reason. Here's my code:
LoginPresenterTest:
#RunWith(MockitoJUnitRunner.class)
public class LoginPresenterTest {
#Mock
LoginMvpView mMockLoginMvpView;
#Mock
DataManager mMockDataManager;
private LoginPresenter<LoginMvpView> mLoginPresenter;
private TestScheduler mTestScheduler;
#BeforeClass
public static void onlyOnce() throws Exception {
}
#Before
public void setUp() throws Exception {
CompositeDisposable compositeDisposable = new CompositeDisposable();
mTestScheduler = new TestScheduler();
TestSchedulerProvider testSchedulerProvider = new TestSchedulerProvider(mTestScheduler);
mLoginPresenter = new LoginPresenter<>(
mMockDataManager,
testSchedulerProvider,
compositeDisposable);
mLoginPresenter.onAttach(mMockLoginMvpView);
}
#Test
public void testServerLoginSuccess() {
String email = "dummy#gmail.com";
String password = "password";
LoginResponse loginResponse = new LoginResponse();
doReturn(Observable.just(loginResponse))
.when(mMockDataManager)
.doServerLoginApiCall(new LoginRequest
.ServerLoginRequest(email, password));
mLoginPresenter.onServerLoginClick(email, password);
mTestScheduler.triggerActions();
verify(mMockLoginMvpView).showLoading();
verify(mMockLoginMvpView).hideLoading();
verify(mMockLoginMvpView).openMainActivity();
}
#After
public void tearDown() throws Exception {
mLoginPresenter.onDetach();
}
Here is my testscheduleprovider :
public class TestSchedulerProvider implements SchedulerProvider {
private final TestScheduler mTestScheduler;
public TestSchedulerProvider(TestScheduler testScheduler) {
this.mTestScheduler = testScheduler;
}
#Override
public Scheduler ui() {
return mTestScheduler;
}
#Override
public Scheduler computation() {
return mTestScheduler;
}
#Override
public Scheduler io() {
return mTestScheduler;
}
}
The error is thrown at LoginPresentertest at the line
.doServerLoginApiCall(new LoginRequest
.ServerLoginRequest(email, password));
Any idea how I can alter this to work?
Thanks!
Apparently your doServerLoginApiCall returns single, but you're trying to mock it with Observable.just(loginResponse). It should be something like Single.just(loginResponse)

RxAndroid, Retrofit 2 unit test Schedulers.io

I am newly learned the RxAndroid but unfortunately the book I studied did not covered any unit test. I have searched a lot on google but failed to find any simple tutorial that cover the RxAndroid unit test in precise way.
I have basically wrote a small REST API using RxAndroid and Retrofit 2. Here is the ApiManager class:
public class MyAPIManager {
private final MyService myService;
public MyAPIManager() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
// set your desired log level
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder b = new OkHttpClient.Builder();
b.readTimeout(35000, TimeUnit.MILLISECONDS);
b.connectTimeout(35000, TimeUnit.MILLISECONDS);
b.addInterceptor(logging);
OkHttpClient client = b.build();
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://192.168.1.7:8000")
.client(client)
.build();
myService = retrofit.create(MyService.class);
}
public Observable<Token> getToken(String username, String password) {
return myService.getToken(username, password)
.subscribeOn(Schedulers.io());
.observeOn(AndroidSchedulers.mainThread());
}
}
I am trying to create a unit test for getToken. Here is my sample test:
public class MyAPIManagerTest {
private MyAPIManager myAPIManager;
#Test
public void getToken() throws Exception {
myAPIManager = new MyAPIManager();
Observable<Token> o = myAPIManager.getToken("hello", "mytoken");
o.test().assertSubscribed();
o.test().assertValueCount(1);
}
}
Due to subscribeOn(Schedulers.io) the above test does not run on main thread due to which it returns 0 value. If I remove subscribeOn(Schedulers.io) from MyAPIManager then it run well and return 1 value. Is there any way to test with Schedulers.io?
Great question and certainly one topic that is lacking a lot of coverage in the community. I would like to share a couple of solutions I personally used and were splendid. These are thought for RxJava 2 but they're available with RxJava 1 just under different names. You will for sure find it if you need it.
RxPlugins and RxAndroidPlugins (this is my favourite so far)
So Rx actually provides a mechanism to change the schedulers provided by the static methods inside Schedulers and AndroidSchedulers. These are for example:
RxJavaPlugins.setComputationSchedulerHandler
RxJavaPlugins.setIoSchedulerHandler
RxJavaPlugins.setNewThreadSchedulerHandler
RxJavaPlugins.setSingleSchedulerHandler
RxAndroidPlugins.setInitMainThreadSchedulerHandler
What these do is very simple. They make sure that when you call i.e. Schedulers.io() the returned scheduler is the one you provide in the handler set in setIoSchedulerHandler. Which scheduler do you want to use? Well you want Schedulers.trampoline(). This means that the code will run on the same thread as it was before. If all schedulers are in the trampoline scheduler, then all will be running on the JUnit thread. After the tests are run, you can just clean the whole thing by calling:
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
I think the best approach to this is to use a JUnit rule. Here's a possible one (sorry for the kotlin syntax):
class TrampolineSchedulerRule : TestRule {
private val scheduler by lazy { Schedulers.trampoline() }
override fun apply(base: Statement?, description: Description?): Statement =
object : Statement() {
override fun evaluate() {
try {
RxJavaPlugins.setComputationSchedulerHandler { scheduler }
RxJavaPlugins.setIoSchedulerHandler { scheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
RxJavaPlugins.setSingleSchedulerHandler { scheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
base?.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
}
}
At the top of your unit test you just need to declare a public attribute annotated with #Rule and instantiated with this class:
#Rule
public TrampolineSchedulerRule rule = new TrampolineSchedulerRule()
in kotlin
#get:Rule
val rule = TrampolineSchedulerRule()
Injecting schedulers (a.k.a. dependency injection)
Another possibility is to inject the schedulers in your classes so at test time you can inject again the Schedulers.trampoline() and in your app you can inject the normal schedulers. This might work for a while, but it will soon become cumbersome when you need to inject loads of schedulers just for a simple class. Here's one way of doing this
public class MyAPIManager {
private final MyService myService;
private final Scheduler io;
private final Scheduler mainThread;
public MyAPIManager(Scheduler io, Scheduler mainThread) {
// initialise everything
this.io = io;
this.mainThread = mainThread;
}
public Observable<Token> getToken(String username, String password) {
return myService.getToken(username, password)
.subscribeOn(io);
.observeOn(mainThread);
}
}
As you can see we can now tell the class the actual schedulers. In your tests you'd do something like:
public class MyAPIManagerTest {
private MyAPIManager myAPIManager;
#Test
public void getToken() throws Exception {
myAPIManager = new MyAPIManager(
Schedulers.trampoline(),
Schedulers.trampoline());
Observable<Token> o = myAPIManager.getToken("hello", "mytoken");
o.test().assertSubscribed();
o.test().assertValueCount(1);
}
}
The key points are:
You want it on the Schedulers.trampoline() scheduler to make sure everything's run on the JUnit thread
You need to be able to modify the schedulers while testing.
That's all. Hope it helps.
=========================================================
Here is Java version which I have used after following above Kotlin example:
public class TrampolineSchedulerRule implements TestRule {
#Override
public Statement apply(Statement base, Description description) {
return new MyStatement(base);
}
public class MyStatement extends Statement {
private final Statement base;
#Override
public void evaluate() throws Throwable {
try {
RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setSingleSchedulerHandler(scheduler -> Schedulers.trampoline());
RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
base.evaluate();
} finally {
RxJavaPlugins.reset();
RxAndroidPlugins.reset();
}
}
public MyStatement(Statement base) {
this.base = base;
}
}
}

RxAndroid - java.lang.IllegalStateException: Another strategy was already registered

I am writing a unit test and need to mock an Observable (from retrofit)
The code in the tested component is as follows:
getApiRequestObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(...)
In the unit test (against the JVM so AndroidSchedulers are not available) I need to make it all synchronous so my tests will look like:
#Test
public void testSomething() {
doReturn(mockedResponse).when(presenter).getApiRequestObservable();
presenter.callApi();
verify(object,times(1)).someMethod();
}
To do this, I should register hooks in a setUp() method:
#Before
public void setUp() throws Exception {
// AndroidSchedulers.mainThread() is not available here so we fake it with this hook
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
// We want synchronous operations
RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook(){
#Override
public Scheduler getIOScheduler() {
return Schedulers.immediate();
}
});
}
But this throws the above exception as I am apparently not allowed to register two hooks. Is there any way around that?
The problem is that you're not resetting test state - you can verify that by running single test. To fix your particular problem you need to reset rx plugins state like so:
#Before
public void setUp(){
RxJavaPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().reset();
//continue setup
...
}
You can even wrap the reset into a reusable #Rule as described by Alexis Mas blog post:
public class RxJavaResetRule implements TestRule {
#Override
public Statement apply(Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
//before: plugins reset, execution and schedulers hook defined
RxJavaPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().reset();
// register custom schedulers
...
base.evaluate();
}
};
}
}

Verify mock interactions within anonymous inner class

I am trying to test my ViewModel in my application, here is the constructor:
#Inject
public SearchUserViewModel(#Named("searchUser") UseCase searchUserUseCase) {
this.searchUserUseCase = searchUserUseCase;
}
In my test I create a SearchUserUseCase with mocks like this:
Observable error = Observable.error(new Throwable("Error"));
when(gitHubService.searchUser(MockFactory.TEST_USERNAME_ERROR)).thenReturn(error);
when(ObserverThread.getScheduler()).thenReturn(Schedulers.immediate());
when(SubscriberThread.getScheduler()).thenReturn(Schedulers.immediate());
searchUserUseCase = new SearchUserUseCase(gitHubService, SubscriberThread, ObserverThread);
In my ViewModel class I have this snippet which I want to test:
public void onClickSearch(View view) {
loadUsers();
}
private void loadUsers() {
if (username == null) {
fragmentListener.showMessage("Enter a username");
} else {
showProgressIndicator(true);
searchUserUseCase.execute(new SearchUserSubscriber(), username);
}
}
private final class SearchUserSubscriber extends DefaultSubscriber<SearchResponse> {
#Override
public void onCompleted() {
showProgressIndicator(false);
}
#Override
public void onError(Throwable e) {
showProgressIndicator(false);
fragmentListener.showMessage("Error loading users");
}
#Override
public void onNext(SearchResponse searchResponse) {
List<User> users = searchResponse.getUsers();
if (users.isEmpty()) {
fragmentListener.showMessage("No users found");
} else {
fragmentListener.addUsers(users);
}
}
}
Finally in my test I have this:
#Test
public void shouldDisplayErrorMessageIfErrorWhenLoadingUsers() {
SearchUserViewModel searchUserViewModel = new SearchUserViewModel(searchUserUseCase);
searchUserViewModel.setFragmentListener(mockFragmentListener);
searchUserViewModel.setUsername(MockFactory.TEST_USERNAME_ERROR);
searchUserViewModel.onClickSearch(view);
verify(mockFragmentListener).showMessage("Error loading users");
}
I get this error from Mockito:
Wanted but not invoked:
fragmentListener.showMessage(
"Error loading users"
);
I am not sure if this is a good test, but I somehow want to test the SearchUserSubscriber one way or another. Thanks
Edit: I have found similar questions to this problem here: Can't verify mock method call from RxJava Subscriber (which still isn't answered) and here: Verify interactions in rxjava subscribers. The latter question is similar but does not execute the subscriber in a separate class (which happens in SearchUserUseCase here).
I also tried RobolectricGradleTestRunner instead of MockitoJunitRunner and changed to Schedulers.io() and AndroidSchedulers.mainThread(), but I still get the same error.
Tried mocking SearchUserUseCase instead of GitHubService (which feels cleaner), but I'm not sure on how to test the subscriber that way since that is passed as an argument to the void method execute() in UseCase.
public void execute(Subscriber useCaseSubscriber, String query) {
subscription = buildUseCase(query)
.observeOn(postExecutionThread.getScheduler())
.subscribeOn(threadExecutor.getScheduler())
.subscribe(useCaseSubscriber);
}
And buildUseCase()
#Override
public Observable buildUseCase(String username) throws NullPointerException {
if (username == null) {
throw new NullPointerException("Query must not be null");
}
return getGitHubService().searchUser(username);
}
For me it worked out to add a Observable.Transformer<T, T> as followed:
void gatherData() {
service.doSomeMagic()
.compose(getSchedulerTransformer())
.subscribe(view::displayValue);
}
private <T> Observable.Transformer<T, T> getSchedulerTransformer() {
if (mTransformer == null) {
mTransformer = (Observable.Transformer<T, T>) observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
return mTransformer;
}
void setSchedulerTransformer(Observable.Transformer<Observable<?>, Observable<?>> transformer) {
mTransformer = transformer;
}
And to set the Transformer. I just passed this:
setSchedulerTransformer(observable -> {
if (observable instanceof Observable) {
Observable observable1 = (Observable) observable;
return observable1.subscribeOn(Schedulers.immediate())
.observeOn(Schedulers.immediate());
}
return null;
});
So just add a #Before method in your test and call presenter.setSchedulerTransformer and it should be able to test this. If you want more detail check this answer.
If you are using Mockito, you can probably get hold of a SearchUserSubscriber using an ArgumentCaptor, for example...
#Captor
private ArgumentCaptor<SearchUserSubscriber> subscriberCaptor;
private SearchUserSubscriber getSearchUserSubscriber() {
// TODO: ...set up the view model...
...
// Execute the code under test (making sure the line 'searchUserUseCase.execute(new SearchUserSubscriber(), username);' gets hit...)
viewModel.onClickSearch(view);
verify(searchUserUseCase).execute(subscriberCaptor.capture(), any(String.class));
return subscriberCaptor.getValue();
}
Now you can have test cases such as...
#Test
public void shouldDoSomethingWithTheSubscriber() {
SearchUserSubscriber subscriber = getSearchUserSubscriber();
...
}

Categories

Resources