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;
}
}
}
Related
I have an ApiRepository class which will contain all my API calls, but currently only has one:
public class RestApiRepository {
private RestClient restClient;
public RestApiRepository(RestClient restClient) {
this.restClient= restClient;
}
public Observable<AuthResponseEntity> authenticate(String header, AuthRequestEntity requestEntity) {
return restClient.postAuthObservable(header, requestEntity);
}
}
And RestClient interface looks like this:
public interface SrsRestClient {
#POST(AUTH_URL)
Observable<AuthResponseEntity> postAuthObservable(#Header("Authorization") String authKey, #Body AuthRequestEntity requestEntity);
}
So, I tried to run the test which passed, but when I generate a code coverage report, that return line of code is red.
Here's my test class:
public class RestApiRepositoryTest {
private RestApiRepository restApiRepository;
#Mock
private RestClient restClient;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
restApiRepository = Mockito.spy(new RestApiRepository(restClient));
}
#Test
public void test_success() {
String token = "token";
AuthRequestEntity requestEntity = new AuthRequestEntity();
AuthResponseEntity responseEntity = new AuthResponseEntity();
Mockito.when(restClient.postAuthObservable(token, requestEntity)).thenReturn(Observable.just(responseEntity));
}
}
I believe the test passed, but nothing is verified, right? Shouldn't this when - then return would be enough?
Personally I wouldn't make the repository a spy, so in setup I'd have:
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
restApiRepository = new RestApiRepository(restClient);
}
Now I'd write the test like:
#Test
public void test_success() {
String token = "token";
AuthRequestEntity requestEntity = new AuthRequestEntity();
AuthResponseEntity responseEntity = new AuthResponseEntity();
Mockito.when(restClient.postAuthObservable(token, requestEntity)).thenReturn(Observable.just(responseEntity));
restApiRepository.authenticate(token, responseEntity)
.test()
.assertValue(responseEntity)
}
This way you are asserting that the observable emits the desired value. test is a handy Rx method that subscribes and creates a test observer that lets you assert on different events.
Also, the reason I wouldn't make the repository a spy is simply because you don't really need to verify any interactions with it, just its dependencies.
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));
}
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)
I cannot make liveData.postValue working in while trying to make unit test. I have been checking in google for a solution and this is the code I have now.
public class ProjectListViewModelTest {
GetProjectList getProjectList = Mockito.mock(GetProjectList.class);
ProjectModel.Project project = new ProjectModel.Project("testing",
"this is a test",
"https://logo.jpg",
new ProjectModel.Company("cat"),
"20150404",
"active");
List<ProjectModel.Project> projects = Arrays.asList(project);
ProjectModel.ProjectList projectsList = new ProjectModel.ProjectList(projects);
ProjectsListViewModel projectsListViewModel;
private PublishSubject<ProjectModel.ProjectList> projectsListPublishSubject = PublishSubject.create();
#Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
#BeforeClass
public static void setUpRxSchedulers() {
Scheduler immediate = new Scheduler() {
#Override
public Disposable scheduleDirect(#NonNull Runnable run, long delay, #NonNull TimeUnit unit) {
return super.scheduleDirect(run, 0, unit);
}
#Override
public Scheduler.Worker createWorker() {
return new ExecutorScheduler.ExecutorWorker(Runnable::run);
}
};
RxJavaPlugins.setInitIoSchedulerHandler(scheduler -> immediate);
RxJavaPlugins.setInitComputationSchedulerHandler(scheduler -> immediate);
RxJavaPlugins.setInitNewThreadSchedulerHandler(scheduler -> immediate);
RxJavaPlugins.setInitSingleSchedulerHandler(scheduler -> immediate);
RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> immediate);
}
#Before
#Throws(exceptionClasses = Exception.class)
public void setUp() {
MockitoAnnotations.initMocks(this);
projectsListViewModel = new ProjectsListViewModel(getProjectList);
when(getProjectList.execute()).thenReturn(projectsListPublishSubject.take(1).singleOrError());
}
#Test
public void testExecuteGetProjectsListSuccess() {
LiveData<List<ProjectModel.MapProject>> liveData = projectsListViewModel.getLiveData();
ProjectModel.MapProject expectedResult = new ProjectModel.MapProject(
"testing", "this is a test", "https://logo.jpg",
"cat", "2015-04-04", "active");
projectsListViewModel.getProjects();
projectsListPublishSubject.onNext(projectsList);
Assert.assertEquals(expectedResult, liveData.getValue().get(0));
}
#After
public void tearDownClass(){
RxAndroidPlugins.reset();
}
The code that I have in setUpRxSchedulers is mandatory in order to avoid the same error (Method getMainLooper in android.os.Looper not mocked) with Rx. But I cannot solve this error that I get when calling liveData.post(projectList). In all the forums that I checked they say that with #Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); the problem should be solved. But is not my case.
I put here the viewmodel as well in case it can help:
public class ProjectsListViewModel extends ViewModel {
GetProjectList getProjectList;
MutableLiveData<List<ProjectModel.MapProject>> liveData = new MutableLiveData<>();
public ProjectsListViewModel(GetProjectList getProjectList){
this.getProjectList = getProjectList;
}
public LiveData<List<ProjectModel.MapProject>> getLiveData(){
return liveData;
}
public void getProjects(){
getProjectList.execute()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(ProjectModel.ProjectList::getProjects)
.toObservable().flatMapIterable(projects -> projects)
.map(project -> project.convertToMapProject()).toList()
.subscribe(projectsList ->
liveData.setValue(projectsList));
}
}
The usage of InstantTaskExecutorRule will actually solve this.
I think the issue is that in JUnit 5 the #Rule annotation doesn't seem to be supported anymore (as Extensions are now the way to go). The code will compile successfully, but the rule just won't be applied.
There are (at least) two solutions to this:
Use JUnit 4
Is definitely the quicker, may not be the best depending on how much you need JUnit 5.
This can be done just by changing the annotation of your setup method from #BeforeEach to #Before and by importing the #Test annotation from JUnit 4.
Here's how your imports should look like.
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Implement a InstantTaskExecutorExtension
This is better if you care about using JUnit 5 :)
Here's an article that talks about how to implement precisely InstantTaskExecutorExtension.
Once that's done remember to apply it to your test class using the #ExtendWith(InstantTaskExecutorExtension::class) annotation instead of #Rule!
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.