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!
Related
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).
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 unable to mock sharedPreference and when i test my presenter then sharepreference instance is null.
#RunWith(MockitoJUnitRunner.class)
public class PreferencesPresenterTest {
#Mock
PreferencesMvpView preferencesMvpView;
#Mock
ApiService apiService;
#Mock
Context context;
#Mock
SchedulerProvider mSchedulerProvider;
PreferencesPresenter mPresenter;
#Before
public void setUp() throws Exception {
CompositeDisposable compositeDisposable = new CompositeDisposable();
mPresenter = new PreferencesPresenter(compositeDisposable, apiService, mSchedulerProvider);
// mPrefences = new AppPreferences();
mPresenter.onAttach(preferencesMvpView);
}
#Test
public void testFilter() throws Exception {
Mockito.when(mSchedulerProvider.getUiScheduler()).thenReturn(Schedulers.trampoline());
Mockito.when(mSchedulerProvider.getWorkerScheduler()).thenReturn(Schedulers.trampoline());
mPresenter.loadPreferenceData();
}
}
//This is the method which i am testing
#Override
public void loadPreferenceData() {
long userId = mPreferences.getLong(AppPreferences.USER_ID);
getMvpView().showLoading();
getCompositeDisposable().add(getApiService().getPreferencesData(userId)
.subscribeOn(getSchedulerProvider().getWorkerScheduler())
.observeOn(getSchedulerProvider().getUiScheduler())
.subscribe(
jsonObject -> {
//Log.d(getClass().getSimpleName(), "PreferencesPresenter : loadPreferenceData: onSuccess");
if (!isViewAttached()) {
return;
}
getMvpView().hideLoading();
if (jsonObject != null && AppUtils.containsValue(jsonObject, JsonKeys.DATA))
setupFieldList(new Gson().fromJson(jsonObject.get(JsonKeys.DATA), Preferences.class));
}
, throwable -> {
// Log.d(getClass().getSimpleName(), "PreferencesPresenter : loadPreferenceData: Error");
if (!isViewAttached()) {
return;
}
getMvpView().hideLoading();
handleApiError(throwable);
}));
}
Are you running unit tests? By default, any calls to the Android framework from within a unit test will throw an exception.
From https://developer.android.com/training/testing/fundamentals#interact-android-environment:
You can control and verify the elements of the Android framework with which your app interacts by running unit tests against a modified version of android.jar, which doesn't contain any code. Because your app's calls to the Android framework throw exceptions by default, you need to stub out every one of these interactions by using a mocking framework, such as Mockito.
You have a few options here:
Encapsulate the code that interacts with SharedPreferences in a separate class and mock out that class with Mockito when testing your Presenter
Use Robolectric, which provides an implementation of the Android SDK
Run instrumented tests (androidTest) instead of unit tests (test)
I would recommend #1, as that approach allows you to continue running fast JUnit tests without having to deal with the overhead of Robolectric.
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.
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;
}
}
}