UntTest - Mock RxJava object in UseCase Object - android

I want to test UseCase object, in this specific case there is a LoginUseCase, which looks like this:
public class LoginUseCase implements RxUseCase<AuthResponse, AuthCredentials> {
ApiManager mApiManager;
public LoginUseCase(ApiManager apiManager) {
mApiManager =apiManager;
}
#Override
public Observable<AuthResponse> execute(final AuthCredentials authCredentials) {
return Observable.just(1)
.delay(750, TimeUnit.MILLISECONDS)
.flatMap(l -> mApiManager.login(authCredentials.getLogin(), authCredentials.getPassword()));
}
}
I wrote simple test:
#RunWith(MockitoJUnitRunner.class)
public class LoginUseCaseTest {
private LoginUseCase mLoginUseCase;
#Mock ApiManager mApiManager;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mLoginUseCase = new LoginUseCase(mApiManager);
}
#Test
public void testShouldThrowsError() throws Exception {
TestSubscriber<AuthResponse> testSubscriber = new TestSubscriber<>();
doReturn(Observable.error(new Throwable())).when(mApiManager).login("", "");
mLoginUseCase
.execute(new AuthCredentials("", ""))
.subscribe(testSubscriber);
testSubscriber.assertNoErrors();
}
}
But this test always passes and I don't know how mock error observable in this case.
EDIT: I've chaged testShouldThrowsError() according to SkinnyJ, but test still passes, any sugestions?

You need to call awaitTerminalEvent() on your test subscriber before assertions.
Because now, you schedule a delay to be run on Schedulers.computation and your test method successfully completes before completion of observable.
Alternative approach would be to pass scheduler as argument to execute method, or store scheduler in your usecase. This way, during test you can pass Schedulers.immediate() and your test will run on current thread (which will block execution for specified delay).
And last approach is to call toBlocking() on observable, but I think that passing scheduler is preferred choice. And there is no way to add this operator to your current observable.

Related

Using Rxjava to insert , delete from room db

i'm working on project where i have to insert and delete data from room db , so basically i was using the old approach which is to implement Asynctask for background operations but since it is no longer recommended , i decided to use Rxjava instead , i tried to implement it but i'm not getting any result so far , and this is a piece of code where it shows the insertion of data
Completable.fromAction(new Action() {
#SuppressLint("CheckResult")
#Override
public void run() throws Exception {
recordingDb.insertRecording(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
And this is the deletion method
public void DeleteData(modelUidd modelUidd) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
recordingDb.delete(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
So basically i tried to use completable with the operator fromaction , i'm not sure if what i implemented is correct or not , any help would appreciated guys , thank you
The problem is that you are actually not subscribing to the observables, so nothing is happening.
To subscribe to an observable, you have to call the .subscribe() method.
I suggest that your methods defined in your DAO classes (or you "repository" classes), such as DeleteData in your example, return the Observable. Then, you can call the method in the DAO to get the Observable and subscribe to it from (ideally) a ViewModel or, if not, directly from an Activity. The moment you call the subscribe you will trigger the actual insertion or deletion, and will get a response from the onSuccess or onError defined callbacks.
For example:
public class MyViewModel extends ViewModel {
private MyRepository myRepository;
private final CompositeDisposable disposables;
#Inject
public MyViewModel(MyRepository myRepository) {
...
this.myRepository = myRepository;
disposables = new CompositeDisposable();
...
}
public void callObservableInRepository() {
disposables.add(myRepository.myObservable()
.subscribe(onSuccess -> {...} , onError -> {...}));
}
#Override
protected void onCleared() {
disposables.clear();
}
}
You can also check these two other answers for more information:
About async operations in RxJava
Using CompositeDisposable in ViewModel

Why does the view methods does not get called inside an Observable which is inside the presenter on a unit test?

I am new to Android Unit Testing and we are currently using MVP+RxJava+Dagger 2. I wrote this test which fails in unit test, but works in production code:
#Override
public void retrieveListOfBillers() {
getMvpView().showLoading();
getCompositeDisposable().add(
getDataManager()
.doServerGetBillersList()
.observeOn(getSchedulerProvider().ui())
.subscribeOn(getSchedulerProvider().io())
.subscribe( response ->{
for (Datum data : response.getData()) {
getMvpView().setUpRecyclerView(enrollmentBillers);
getMvpView().showDefaultViews();
getMvpView().hideLoading();
}, throwable -> {
...
And this is how I do it in the test:
#Test
public void testGetListOfBillersCallsSetupRecyclerView(){
mPresenter.retrieveListOfBillers();
verify(mView).showLoading();
verify(mView).setUpRecyclerView(anyList());
}
This is how I instanciated the setup for the test:
#Before
public void setUp() {
// 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);
CompositeDisposable compositeDisposable = new CompositeDisposable();
mTestScheduler = new TestScheduler();
testSchedulerProvider = new TestSchedulerProvider(mTestScheduler);
mPresenter = new CreateBillerContactPresenter<>(
dataManager,
testSchedulerProvider,
compositeDisposable
);
mPresenter.onAttach(mView);
when(dataManager.doServerGetBillersList()).thenReturn(Observable.just(getBillerListResponse));
I believe it has something to do with the TestScheduler but I need someone who actually knows what is the problem here, which is why my test code fails to call setupRecyclerView, and other expected view method calls from the presenter?
I have found the answer:
It seems the TestScheduler class have a triggerAction method in which:
"Triggers any actions that have not yet been triggered and that are scheduled to be triggered at or before this Scheduler's present time." -- from comments above the method.
Then the presenter/datamanager calls the view methods as expected.

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

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));

Categories

Resources