Unit testing Realm wrapped LiveData with Mockito - android

I am trying out Realm along with Android architecture components including LiveData.
I have been following Google's Guide to Application Architecture:
https://developer.android.com/topic/libraries/architecture/guide.html
...substituting Room with Realm.
I have everything working from an implementation perspective but have run into an issue with Mockito I cannot yet resolve when trying to write unit tests.
I have shown my test below with some commented out lines along with explanations of what I have tried so far and the result:
#Test
public void loadCustomModelObjectsFromNetwork() throws IOException {
// Prepare DAO
MutableLiveData<List<CustomModelObject>> dbData = new MutableLiveData<>();
// Compilation error
//when(dao.getCustomModelObjects()).thenReturn(dbData);
// Runtime exc.
//doReturn(dbData).when(dao).getCustomModelObjects();
// Runtime exc.
//java.lang.ClassCastException:
//android.arch.lifecycle.MutableLiveData cannot be cast to LiveRealmResults
doAnswer(new Answer() {
#Override
public Object answer(InvocationOnMock i) throws Throwable {
// Made various attempts to convert to LiveRealmResults here
return dbData;
}
}).when(dao).getCustomModelObjects();
/*
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
MutableLiveData cannot be returned by getCustomModelObjectss()
getCustomModelObjects() should return LiveRealmResults
*/
// Prepare REST service response
List<CustomModelObject> customModelObjects = new ArrayList<>();
CustomModelObject repo = new CustomModelObject();
repo.setDescription("Desc1");
customModelObjects.add(repo);
CustomModelObjectsResponse response = new CustomModelObjectsResponse();
response.setCustomModelObjects(customModelObjects);
DocumentWrapper<CustomModelObjectsResponse> items = new DocumentWrapper<>();
items.setBody(response);
LiveData<ApiResponse<DocumentWrapper<CustomModelObjectsResponse>>> call = successCall(items);
when(retrofitService.getCustomModelObjects()).thenReturn(call);
when(service.getService()).thenReturn(retrofitService);
// Item under test
LiveData<Resource<List<CustomModelObject>>> data = repository.getCustomModelObjects();
// Assertions
verify(dao).getCustomModelObjects();
verifyNoMoreInteractions(service);
Observer observer = mock(Observer.class);
data.observeForever(observer);
verifyNoMoreInteractions(service);
verify(observer).onChanged(Resource.loading(null));
MutableLiveData<List<CustomModelObject>> updatedDbData = new MutableLiveData<>();
//when(dao.getCustomModelObjects()).thenReturn(updatedDbData);
doReturn(updatedDbData).when(dao).getCustomModelObjects();
dbData.postValue(null);
verify(retrofitService).getCustomModelObjects();
verify(dao).save(customModelObjects);
updatedDbData.postValue(customModelObjects);
verify(observer).onChanged(Resource.success(repo));
}
Even though in my implementation it works fine and LiveData<List<CustomModelObject>> can be derived from LiveRealmResults<CustomModelObject> in the unit test I cannot seem to get this to work with Mockito.
More implementation details about my setup can be found here:
Using Realm and LiveData. Converting LiveData<RealmResults<CustomModelObject>> to LiveData<List<CustomModelObject>>
Thanks,
Paul.
UPDATE
when(dao.getCustomModelObjects()).thenReturn(dbData);
has the following compilation error:
error: no suitable method found for
thenReturn(MutableLiveData<List<CustomModelObject>>)
method OngoingStubbing.thenReturn(LiveRealmResults<CustomModelObject>) is not applicable
(argument mismatch; MutableLiveData<List<CustomModelObject>> cannot be converted to LiveRealmResults<CustomModelObject>)
method OngoingStubbing.thenReturn(LiveRealmResults<CustomModelObject>,LiveRealmResults<CustomModelObject>...) is not applicable
(argument mismatch; MutableLiveData<List<CustomModelObject>> cannot be converted to LiveRealmResults<CustomModelObject>)

public RealmLiveData<CustomModelObject> getCustomModelObjects() {
return asLiveData(realm.where(CustomModelObject.class).findAllAsync());
}
should be
public LiveData<List<<CustomModelObject>> getCustomModelObjects() {
return asLiveData(realm.where(CustomModelObject.class).findAllAsync());
}
Then your when(...) should no longer have compilation error.

Related

Android: How can i unit test Rxjava Observable?

I'm studying android unit testing and i'm a bit stuck of unit testing for rxjava observable.
This method i'm trying to test:
#Override
public Observable<AuthenticationUsingSqlEntity> logInUsingSql(String token, String ssid) {
SqlRepo sqlRepo = new SqlRepo();
return sqlRepo.authenticate(token, ssid);
}
I have created simple ArgumentCaptor to test that input already the same and have been passed on unit testing what i'm trying to do is to test sql retrofit response but i can't.
I wonder why you provide some method call, but not the actual SqlRepo method to test? The result is, that this only permits for a general answer. And it's unclear, which version you're talking about; I'd assume version 2. However, one can simply chain Observable with .test(), in order to test the Observable itself and there's TestObserver, but there's also TestSubscriber, which can be used to test subscribers and TestScheduler, which can be used to schedule mock RX events. It should be obvious, that the method call which've provided will not suffice to write a proper test, as the method to test is entirely unknown (to me).
In your test on that method, you shouldn't use ArgumentCaptor. Normally, it is used when you need to write a test on a method which receives or creates an object and you want to check the validity of that object as a parameter (for example, that an email address is formatted correctly).
// Create a mock of the OtherClass
OtherClass other = mock(OtherClass.class);
// Run the foo method with the mock
new A().foo(other);
// Capture the argument of the doSomething function
ArgumentCaptor<SomeData> captor = ArgumentCaptor.forClass(SomeData.class);
verify(other, times(1)).doSomething(captor.capture());
// Assert the argument
SomeData actual = captor.getValue();
assertEquals("Some inner data", actual.innerData);
The only test on that method that you can use is a test that checks if the method calls sqlRepo.authenticate. If you want to check the response from Retrofit, then you need to write a test specifically on the method 'authenticate' in the class SqlRepo. Also, if you want to write a test specifically on Observable, then you can use TestObserver. You can then use various methods to check the result you'll receive.
Here's an example:
someService.authenticate()
.test()
.assertNoErrors()
.assertValue( someValue ->
//check if the value is correct
);
Assuming your class is similar to following:
class LoginManager {
#Override
public Observable<AuthenticationUsingSqlEntity> logInUsingSql(String token, String ssid) {
SqlRepo sqlRepo = new SqlRepo();
return sqlRepo.authenticate(token, ssid);
}
}
then with unit testing we are aiming to verify the logic of a unit or function.
in that perspective the logic of your method is that you we offload the login operation to SqlRepo class. And we have to verify this logic.
So as to do that we check whether authenticate function of Sqlrepo is being invoked in response to the invokation of logInUsingSql() method.
we do that by :
loginManagerClassUnderTest = Mockito.spy(loginManagerClassUnderTest);
mockSqlRepo mockSqlRepo = Mockito.mock(mockSqlRepo.class);
doReturn(mockSqlRepo).when(loginManagerClassUnderTest).getSqlRepo();
loginManagerClassUnderTest.logInUsingsql("mockToken", "mockSsid");
ArgumentCaptor<String> tokenCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> ssidCaptor = ArgumentCaptor.forClass(String.class);
verify(mockSqlRepo).authenticate(tokenCaptor.capture(), ssidCaptor.capture());
assertEquals("mockToken", tokenCaptor.getValue());
assertEquals("mockSsid", ssidCaptor.getValue());
As you can see that LoginManager is dependent on SqlRepo class and we have to provide a mock for this dependency so that we ensure that test is carried out in isolation.
so change Loginmanager Function to :
class LoginManager {
#Override
public Observable<AuthenticationUsingSqlEntity> logInUsingSql(String token, String ssid) {
SqlRepo sqlRepo = getSqlRepo();
return sqlRepo.authenticate(token, ssid);
}
public SqlRepo getSqlRepo() {
return new SqlRepo();
}
}

Unit Testing: How to verify and mock onCompleted for an Observable in RxJava within Android

I am trying to write some unit tests for an Android application that is using Retrofit 2, Mockito 1.10 and RXJava 1.0. I am not using a java version that supports lambdas!
My code uses Observables and I can do the following:
when(myAPI.Complete(anyString(), any(MyContainer.class)))
.thenReturn(Observable.<GenericResponse>error(new Throwable("An error has occurred!")));
Subscriber genericResponseSubscriber = mock(Subscriber.class);
myPresenter.myUseCase(id, container, genericResponseSubscriber);
verify(genericResponseSubscriber, times(1)).onError(any(Throwable.class));
The above code works fine and allows me to throw an error and capture it within the test.
What I need to be able to do as well (of course) :) is to capture positive conditions. I feel like it's obvious but can't find the answer I need.
How can I capture onComplete and onNext cases ?
I know that the verification for onComplete would be...
verify(genericResponseSubscriber, times(1)).onCompleted();
But I can't see what my 'when' clause should be. I tried the following but that fails:
GenericResponse response = new GenericResponse();
response.setSuccess(true);
when(myAPI.orderComplete(anyString(), any(MyContainer.class)))
.thenReturn(Observable.just(response));
Subscriber genericResponseSubscriber = mock(Subscriber.class);
myPresenter.myUseCase(id, container, genericResponseSubscriber);
verify(genericResponseSubscriber, times(1)).onCompleted();
The failure here is that subscriber.onStart() was instead called.
So, what I would like to know is, how I can mock and verify the 'onComplete' and 'onNext' calls, please and more importantly what I should have looked to be able to have resolved this myself rather than having to ask! :)
As always, any help is appreciated.
Edit..
My onError working test case..
public void UseCaseOnError() throws Exception {
String id = "5";
 
order Order = new Order();
SomeContainer myContainer = new SomeContainer(order);
 
when(myRetroFitAPI.complete(anyString(), any(SomeContainer.class)))
.thenReturn(Observable.error(new Throwable(“My error!")));
 
Subscriber genericResponseSubscriber = mock(Subscriber.class);
 
orderPresenter.doUseCase(id, myContainer, genericResponseSubscriber);
 
verify(genericResponseSubscriber,times(1)).onError(any(Throwable.class));
 
}
What I should really add is that, I feel there should be an equivalent for onError in terms of a positive state, i.e. onCompleted. If I do the same but with onCompleted instead, my verification fails as it detects onStart has been called instead which I am finding rather confusing.
I have tried using the ReplaySubject as such:
public void createOrderOnCompleteError() {
orderOnCompleteSubject.onError(new Throwable("I am an error"));
}
public void createOrderOnCompleteSuccess() {
orderOnCompleteSubject.onNext(new GenericResponse().setSuccess(true));
orderOnCompleteSubject.onCompleted();
}
The error mechanism works fine.. the completed mechanism does not...
You should use the class TestObserver for testing the Observable, in this way:
public Observable<Integer> getObservable() {
return Observable.just(12, 20, 330);
}
#Test
public void testObservable() {
Observable<Integer> obs = getObservable();
TestObserver<Integer> testObserver = TestObserver.create();
obs.subscribe(testObserver);
testObserver.assertComplete();
testObserver.assertResult(12, 20, 330);
}
In this way you can verify that it completes and emits all the expected items.
If you want to create a mocked version of your observable, you can just create a new Observable that has the behaviour that you want. For example:
public Observable<Integer> mockedObservableCompleteWithResult() {
return Observable.create(e -> {
e.onNext(12);
e.onNext(20);
e.onNext(330);
e.onComplete();
});
}
that can be verified with the above-mentioned test.
Then we can create other mock for modelling other results
public Observable<Integer> mockedObservableError() {
return Observable.create(e -> {
e.onNext(12);
e.onError(new Throwable("Generic exception"));
});
}
That can be verified:
#Test
public void testObservable() throws Exception {
Observable<Integer> obs = mockedObservableError();
TestObserver<Integer> testObserver = TestObserver.create();
obs.subscribe(testObserver);
testObserver.assertError(Throwable.class);
}
Instead of mocking the Subscriber, you should create a TestSubscriber for RxJava 1:
when(myAPI.Complete(anyString(), any(MyContainer.class)))
.thenReturn(Observable.<GenericResponse>error(new Throwable("An error has occurred!")));
TestSubscriber genericResponseSubscriber = TestSubscriber.create();
myPresenter.myUseCase(id, container, genericResponseSubscriber);
// To check for an error
genericResponseSubscriber.assertError(Throwable.class)
// To check for completion
genericResponseSubscriber.assertCompleted()
You might need to be a bit more specific about which error class you expect. Check out the TestSubscriber documention. There is tons of more stuff you can verify with this class.
Happy testing!
The easy way is to try throwing a mock exception than the real one.
#Mock
Exception mockException;
observer.onError(mockException);

Understanding how to test RxJava Observables

I am learning how to write android unit tests. And, I am looking at examples: So, I saw something like this:
#Test
public void getPopularMoviesMakesApiCall() {
// given that the api service returns a response
1. when(apiService.discover(SORT_BY_POPULARITY, PAGE, API_KEY)).thenReturn(Observable.just(mDiscoverMoviesResponse));
// when getPopularMovies is invoked
2. mRemoteRepository.getPopularMovies(1).subscribeWith(mMovieListTestSubscriber);
// then, verify that the api request is made and returns the expected response
3. verify(apiService).discover(SORT_BY_POPULARITY, PAGE, API_KEY);
4. mMovieListTestSubscriber.assertValue(mMovieList);
}
I tried to run it, and I noticed option 1 executes always, option 2 does too. But, if option 3 doesn't comform with the information in option 2,
it throws an error saying they aren't the same. Which means option 3 confirms option 2. If I'm wrong or there's anything to correct, please
do tell. So, I wrote something like this:
#Test
public void testBadHashException() throws Exception {
1. mRemoteRepository.getPopularMovies(1, FAKE_API_KEY).subscribeWith(mMovieListTestSubscriber);
2. mMovieListTestSubscriber.assertNoValues();
3. mMovieListTestSubscriber.assertError(HttpException.class);
}
This is what I noticed:
private List<Movie> mMovieList;
private DiscoverMoviesResponse mDiscoverMoviesResponse;
private MoviesRepository mRemoteRepository;
private TestObserver<List<Movie>> mMovieListTestSubscriber;
private TestObserver<Movie> mMovieTestSubscriber;
#Mock
MovieApiService apiService;
Those above, were declared at the top, and initialized by a Mockito #Before #annotation like this:
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mRemoteRepository = new MoviesRemoteRepository(apiService);
mMovieTestSubscriber = new TestObserver<>();
mMovieListTestSubscriber = new TestObserver<>();
mMovieList = TestDataGenerator.generateMovieList(10);
mDiscoverMoviesResponse = TestDataGenerator.generateDiscoverMoviesResponse(mMovieList);
}
Note: TestDataGenerator is a helper class for generating data. As it's done there, he got MovieList and then got another which is the main response body.
APIService: The retrofit service class.
MoviesRepository: An helper class for manipulating Observables in the service class. Which is used by the ViewModel.
The second test keeps giving me java.lang.RuntimeException: No mock defined for invocation. I don't seem to understand it yet.
Is there a specific instance where I should use when, verify, how do I test for Observable Retrofit Request Errors.
If it's saying no Mock data, but then Mock data has been generated when this is done. Or is it supposed to be mocked differently?
mMovieList = TestDataGenerator.generateMovieList(10);
mDiscoverMoviesResponse = TestDataGenerator.generateDiscoverMoviesResponse(mMovieList);
More on my observation:
I was going through Mockito and I noticed, the first test that went through was executing because he did:
1. when(apiService.discover(SORT_BY_POPULARITY, PAGE, API_KEY)).thenReturn(Observable.just(mDiscoverMoviesResponse));
Since the error for the second function shows java.lang.RuntimeException: No mock defined for invocation, it was stated that the method
within a class can be mocked by using when("some method").thenReturn() it's okay. I then modified my testBadHashException to look like this:
#Test
public void testBadHashException() throws Exception {
0. when(apiService.discover(SORT_BY_POPULARITY, PAGE, API_KEY)).thenReturn(Observable.just(mDiscoverMoviesResponse));
1. mRemoteRepository.getPopularMovies(1, FAKE_API_KEY).subscribeWith(mMovieListTestSubscriber);
2. mMovieListTestSubscriber.assertNoValues();
3. mMovieListTestSubscriber.assertError(HttpException.class);
}
Instead of it throwing an exception, it threw a success.
I rewrote the error test:
#Test
public void getPopularMoviesThrowsError() {
when(mMoviesRepository.getPopularMovies(PAGE)).thenReturn(Observable.<List<Movie>>error(new TimeoutException()));
// request movies
mMoviesViewModel.discoverMovies(true);
verify(mMoviesRepository).getPopularMovies(PAGE);
// check that empty view is hidden
assertFalse(mMoviesViewModel.emptyViewShowing.get());
// check that loading view is hidden
assertFalse(mMoviesViewModel.moviesLoading.get());
// check that error view is showing
assertTrue(mMoviesViewModel.errorViewShowing.get());
}
There is a compilation error here: when(mMoviesRepository.getPopularMovies(PAGE)).thenReturn(Observable.<List<Movie>>error(new TimeoutException()));
It cannot resolve method here: Observable.<List<Movie>>error(new TimeoutException())
Writing tests in Android looks really weird compared to JavaScript. Any help on how I can learn or achieve understanding how to write unit testing would be appreciated. I just adopted
the MVVM pattern and I'm trying to write test with it. Thanks.
If you need to send an error to your Observable you can create it like this:
when(mMoviesRepository.getPopularMovies(PAGE)).thenReturn(
Observable.create(new Observable.OnSubscribe<SomeClass>() {
#Override public void call(Subscriber<SomeCladd> subscriber) {
subscriber.onError(new Exception("some message!"));
}
}));
This way you will have a Observable that returns an error so you can call
mMovieListTestSubscriber.assertError
The problem that you are having is that you using Observable.just to create your Observable... So the method onError is never going to be called, just the onNext.

Realm Unit Testing

I am trying to unit test Realm and its interactions but things are not going too well. I have included all dependencies and keep getting vague failures, below is my code for the Helper class which is a wrapper over Realm.
Questions
Is this the correct way of testing Realm?
How can I test data that is in the app's sandbox, can that data only be tested by UI/Instrumentation tests?
I am getting an error currently (below) and before I was getting a "Powermock zero args constructor doesn't exist"
GitHub repo
Below is the current code I have for my Unit test:
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, application = CustomApplicationTest.class)
#PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "javax.crypto.","java.security.*"})
#SuppressStaticInitializationFor("io.realm.internal.Util")
#PrepareForTest({Realm.class, RealmConfiguration.class,
RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
public class DatabaseHelperTest {
#Rule
public PowerMockRule rule = new PowerMockRule();
private DatabaseHelper dB;
private Realm realmMock;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockStatic(Realm.class);
mockStatic(RealmConfiguration.class);
mockStatic(RealmCore.class);
mock(DatabaseHelper.class);
final Realm mockRealm = PowerMockito.mock(Realm.class);
realmMock = mockRealm;
final RealmConfiguration mockRealmConfig = PowerMockito.mock(RealmConfiguration.class);
doNothing().when(RealmCore.class);
RealmCore.loadLibrary(any(Context.class));
whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig);
when(Realm.getInstance(any(RealmConfiguration.class))).thenReturn(mockRealm);
when(Realm.getDefaultInstance()).thenReturn(mockRealm);
when(Realm.getDefaultInstance()).thenReturn(realmMock);
when(realmMock.createObject(Person.class)).thenReturn(new Person());
Person person = new Person();
person.setId("2");
person.setName("Jerry");
person.setAge("25");
Person person2 = new Person();
person.setId("3");
person.setName("Tom");
person.setAge("22");
List<Person> personsList = new ArrayList<>();
personsList.add(person);
personsList.add(person2);
RealmQuery<Person> personRealmQuery = mockRealmQuery();
when(realmMock.where(Person.class)).thenReturn(personRealmQuery);
RealmResults<Person> personRealmResults = mockRealmResults();
when(realmMock.where(Person.class).findAll()).thenReturn(personRealmResults);
when(personRealmResults.iterator()).thenReturn(personsList.iterator());
when(personRealmResults.size()).thenReturn(personsList.size());
when(realmMock.copyFromRealm(personRealmResults)).thenReturn(personsList);
realmMock = mockRealm;
dB = new DatabaseHelper(realmMock);
}
#Test
public void insertingPerson(){
doCallRealMethod().when(realmMock).executeTransaction(any(Realm.Transaction.class));
Person person = mock(Person.class);
when(realmMock.createObject(Person.class)).thenReturn(person);
dB.putPersonData();
verify(realmMock, times(1)).createObject(Person.class);
verify(person, times(1)).setId(anyString());
}
#Test
public void testExistingData(){
List<Person> personList = dB.getPersonList();
//NPE if checking person object properties i.e name, id. Only list size is available why?
Assert.assertEquals(2, personList.size());
}
#SuppressWarnings("unchecked")
private <T extends RealmObject> RealmQuery<T> mockRealmQuery() {
return mock(RealmQuery.class);
}
#SuppressWarnings("unchecked")
private <T extends RealmObject> RealmResults<T> mockRealmResults() {
return mock(RealmResults.class);
}
Error:
org.mockito.exceptions.misusing.NotAMockException:
Argument passed to verify() is of type Realm$$EnhancerByMockitoWithCGLIB$$317bc746 and is not a mock!
Make sure you place the parenthesis correctly!
See the examples of correct verifications:
verify(mock).someMethod();
verify(mock, times(10)).someMethod();
verify(mock, atLeastOnce()).someMethod();
at com.appstronomy.realmunittesting.db.DatabaseHelperTest.insertingPerson(DatabaseHelperTest.java:133)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Is this the correct way of testing Realm?
How about following the official tests. While instrumentation tests seem easy, unit test are quite involved:
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21)
#PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
#SuppressStaticInitializationFor("io.realm.internal.Util")
#PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
public class ExampleActivityTest {
// Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock
#Rule
public PowerMockRule rule = new PowerMockRule();
private Realm mockRealm;
private RealmResults<Person> people;
#Before
public void setup() throws Exception {
// Setup Realm to be mocked. The order of these matters
mockStatic(RealmCore.class);
mockStatic(RealmLog.class);
mockStatic(Realm.class);
mockStatic(RealmConfiguration.class);
Realm.init(RuntimeEnvironment.application);
// Create the mock
final Realm mockRealm = mock(Realm.class);
final RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class);
// TODO: Better solution would be just mock the RealmConfiguration.Builder class. But it seems there is some
// problems for powermock to mock it (static inner class). We just mock the RealmCore.loadLibrary(Context) which
// will be called by RealmConfiguration.Builder's constructor.
doNothing().when(RealmCore.class);
RealmCore.loadLibrary(any(Context.class));
// TODO: Mock the RealmConfiguration's constructor. If the RealmConfiguration.Builder.build can be mocked, this
// is not necessary anymore.
whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig);
// Anytime getInstance is called with any configuration, then return the mockRealm
when(Realm.getDefaultInstance()).thenReturn(mockRealm);
// Anytime we ask Realm to create a Person, return a new instance.
when(mockRealm.createObject(Person.class)).thenReturn(new Person());
// Set up some naive stubs
Person p1 = new Person();
p1.setAge(14);
p1.setName("John Young");
Person p2 = new Person();
p2.setAge(89);
p2.setName("John Senior");
Person p3 = new Person();
p3.setAge(27);
p3.setName("Jane");
Person p4 = new Person();
p4.setAge(42);
p4.setName("Robert");
List<Person> personList = Arrays.asList(p1, p2, p3, p4);
// Create a mock RealmQuery
RealmQuery<Person> personQuery = mockRealmQuery();
// When the RealmQuery performs findFirst, return the first record in the list.
when(personQuery.findFirst()).thenReturn(personList.get(0));
// When the where clause is called on the Realm, return the mock query.
when(mockRealm.where(Person.class)).thenReturn(personQuery);
// When the RealmQuery is filtered on any string and any integer, return the person query
when(personQuery.equalTo(anyString(), anyInt())).thenReturn(personQuery);
// RealmResults is final, must mock static and also place this in the PrepareForTest annotation array.
mockStatic(RealmResults.class);
// Create a mock RealmResults
RealmResults<Person> people = mockRealmResults();
// When we ask Realm for all of the Person instances, return the mock RealmResults
when(mockRealm.where(Person.class).findAll()).thenReturn(people);
// When a between query is performed with any string as the field and any int as the
// value, then return the personQuery itself
when(personQuery.between(anyString(), anyInt(), anyInt())).thenReturn(personQuery);
// When a beginsWith clause is performed with any string field and any string value
// return the same person query
when(personQuery.beginsWith(anyString(), anyString())).thenReturn(personQuery);
// When we ask the RealmQuery for all of the Person objects, return the mock RealmResults
when(personQuery.findAll()).thenReturn(people);
// The for(...) loop in Java needs an iterator, so we're giving it one that has items,
// since the mock RealmResults does not provide an implementation. Therefore, anytime
// anyone asks for the RealmResults Iterator, give them a functioning iterator from the
// ArrayList of Persons we created above. This will allow the loop to execute.
when(people.iterator()).thenReturn(personList.iterator());
// Return the size of the mock list.
when(people.size()).thenReturn(personList.size());
this.mockRealm = mockRealm;
this.people = people;
}
#Test
public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() {
doCallRealMethod().when(mockRealm).executeTransaction(Mockito.any(Realm.Transaction.class));
// Create activity
ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get();
assertThat(activity.getTitle().toString(), is("Unit Test Example"));
// Verify that two Realm.getInstance() calls took place.
verifyStatic(times(2));
Realm.getDefaultInstance();
// verify that we have four begin and commit transaction calls
// Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649
//verify(mockRealm, times(4)).executeTransaction(Mockito.any(Realm.Transaction.class));
// Click the clean up button
activity.findViewById(R.id.clean_up).performClick();
// Verify that begin and commit transaction were called (been called a total of 5 times now)
// Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649
//verify(mockRealm, times(5)).executeTransaction(Mockito.any(Realm.Transaction.class));
// Verify that we queried for Person instances five times in this run (2 in basicCrud(),
// 2 in complexQuery() and 1 in the button click)
verify(mockRealm, times(5)).where(Person.class);
// Verify that the delete method was called. Delete is also called in the start of the
// activity to ensure we start with a clean db.
verify(mockRealm, times(2)).delete(Person.class);
// Call the destroy method so we can verify that the .close() method was called (below)
activity.onDestroy();
// Verify that the realm got closed 2 separate times. Once in the AsyncTask, once
// in onDestroy
verify(mockRealm, times(2)).close();
}
OLDER ANSWER
https://medium.com/#q2ad/android-testing-realm-2dc1e1c94ee1 has a great proposal: do not mock Realm, but use a temporary instance instead. Original proposition with dependency injection: Use
RealmConfiguration testConfig =
new RealmConfiguration.Builder().
inMemory().
name("test-realm").build();
Realm testRealm = Realm.getInstance(testConfig);
If dependency injection is not possible, you could use
Realm.setDefaultConfiguration(testConfig);
instead, which sets the Realm returned by Realm.getDefaultInstance().
EDIT: If you receive a java.lang.IllegalStateException, remember to call Realm.init(InstrumentationRegistry.getTargetContext()) beforehand, and put the files inside the android-test directory. (that is: use an instrumentation test, not a unit test).
My goal is to test my business logic that depends on Realm. Realm.init kept throwing exceptions like:
java.lang.IllegalStateException: Context.getFilesDir() returns /data/user/0/com.pornhub.android.test/files which is not an existing directory. See https://issuetracker.google.com/issues/36918154
at io.realm.Realm.checkFilesDirAvailable(Realm.java:256)
at io.realm.Realm.init(Realm.java:199)
at com.headcheckhealth.headcheck.GenericRealmTest.<init>(RealmTest.kt:99)
...
to get Realm working, use getTargetContext() instead of getContext().
package com.github.ericytsang
import android.app.Instrumentation
import androidx.test.platform.app.InstrumentationRegistry
import io.realm.Realm
import io.realm.RealmConfiguration
import org.junit.Test
/**
* tests to see how [Realm] works. nothing domain-specific here.
*
* cannot use Robolectric because https://github.com/robolectric/robolectric/issues/1389 :(
* need to use Android instrumented tests.
*/
class GenericRealmTest {
/**
* [Realm.init] needs [Instrumentation.getTargetContext] to work; [Instrumentation.getContext]
* will not work.
*/
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val db = run {
Realm.init(context)
Realm.getInstance(
RealmConfiguration.Builder()
.inMemory()
.name("realm.db")
.build()
)
}
#Test
fun teardown_works() = Unit
}

Realm Unit Test on Android

Trying to unit test a class that does some calls to Realm (0.87.4), the test setup fails with
java.lang.NoClassDefFoundError: rx/Observable$OnSubscribe
at io.realm.RealmConfiguration$Builder.<init>(RealmConfiguration.java:279)
at org.testapp.db.MyClassTest.setUp(MyClassTest.java:34)
...
Caused by: java.lang.ClassNotFoundException: rx.Observable$OnSubscribe
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
My test class starts with:
#RunWith(MockitoJUnitRunner.class)
public class MyClassTest extends TestCase {
#Rule
public TemporaryFolder testFolder = new TemporaryFolder();
Realm realm;
#Before
public void setUp() throws Exception {
File tempFolder = testFolder.newFolder("realmdata");
RealmConfiguration config = new RealmConfiguration.Builder(tempFolder).build();
realm = Realm.getInstance(config);
}
...
My gradle has:
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:1.10.19"
testCompile "org.robolectric:robolectric:3.0"
compile 'io.realm:realm-android:0.87.4'
How to solve this?
=== EDIT 1 ===
I added to my gradle:
testCompile 'io.reactivex:rxjava:1.1.0'
and
android {
// ...
testOptions {
unitTests.returnDefaultValues = true
}
}
the new error is
java.lang.UnsatisfiedLinkError: no realm-jni in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1886)
at java.lang.Runtime.loadLibrary0(Runtime.java:849)
at java.lang.System.loadLibrary(System.java:1088)
at io.realm.internal.RealmCore.loadLibrary(RealmCore.java:117)
Unit tests are hard or impossible when using Realm in the class that you are testing (thanks Dmitry for mentioning). What I can do is run the tests as instrumental tests (thanks Dmitry, Christian).
And that is quite easy, I won't have to change anything to the test methods...
A. Move the test class into an "androidTest" folder, instead of "test". (Since Android Studio 1.1 you should put your Unit tests in /src/test and Android Instrumentation Tests in /src/androidTest)
B. Add the dependencies for instrumental tests in the gradle build file, use "androidTest" because they're instrumental:
androidTestCompile 'junit:junit:4.12'
androidTestCompile 'io.reactivex:rxjava:1.1.0'
androidTestCompile 'com.android.support.test:runner:0.4.1'
androidTestCompile 'com.android.support.test:rules:0.4.1'
androidTestCompile 'com.android.support:support-annotations:23.1.1'
C. In the test class, replace the runner at the top with AndroidJUnit4:
#RunWith(AndroidJUnit4.class)
public class MyClassTest extends TestCase {
...
Create an Android run configuration of type "Android Tests", run it and voila, it will test the same methods fine now, but on a device. Makes me very happy.
Since Realm 0.87 you also need to include RxJava to your dependencies:
compile 'io.reactivex:rxjava:1.1.0'
The official tests show how to do this. While instrumentation tests seem easy (as you found out), unit test are quite involved:
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21)
#PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
#SuppressStaticInitializationFor("io.realm.internal.Util")
#PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
public class ExampleActivityTest {
// Robolectric, Using Power Mock https://github.com/robolectric/robolectric/wiki/Using-PowerMock
#Rule
public PowerMockRule rule = new PowerMockRule();
private Realm mockRealm;
private RealmResults<Person> people;
#Before
public void setup() throws Exception {
// Setup Realm to be mocked. The order of these matters
mockStatic(RealmCore.class);
mockStatic(RealmLog.class);
mockStatic(Realm.class);
mockStatic(RealmConfiguration.class);
Realm.init(RuntimeEnvironment.application);
// Create the mock
final Realm mockRealm = mock(Realm.class);
final RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class);
// TODO: Better solution would be just mock the RealmConfiguration.Builder class. But it seems there is some
// problems for powermock to mock it (static inner class). We just mock the RealmCore.loadLibrary(Context) which
// will be called by RealmConfiguration.Builder's constructor.
doNothing().when(RealmCore.class);
RealmCore.loadLibrary(any(Context.class));
// TODO: Mock the RealmConfiguration's constructor. If the RealmConfiguration.Builder.build can be mocked, this
// is not necessary anymore.
whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig);
// Anytime getInstance is called with any configuration, then return the mockRealm
when(Realm.getDefaultInstance()).thenReturn(mockRealm);
// Anytime we ask Realm to create a Person, return a new instance.
when(mockRealm.createObject(Person.class)).thenReturn(new Person());
// Set up some naive stubs
Person p1 = new Person();
p1.setAge(14);
p1.setName("John Young");
Person p2 = new Person();
p2.setAge(89);
p2.setName("John Senior");
Person p3 = new Person();
p3.setAge(27);
p3.setName("Jane");
Person p4 = new Person();
p4.setAge(42);
p4.setName("Robert");
List<Person> personList = Arrays.asList(p1, p2, p3, p4);
// Create a mock RealmQuery
RealmQuery<Person> personQuery = mockRealmQuery();
// When the RealmQuery performs findFirst, return the first record in the list.
when(personQuery.findFirst()).thenReturn(personList.get(0));
// When the where clause is called on the Realm, return the mock query.
when(mockRealm.where(Person.class)).thenReturn(personQuery);
// When the RealmQuery is filtered on any string and any integer, return the person query
when(personQuery.equalTo(anyString(), anyInt())).thenReturn(personQuery);
// RealmResults is final, must mock static and also place this in the PrepareForTest annotation array.
mockStatic(RealmResults.class);
// Create a mock RealmResults
RealmResults<Person> people = mockRealmResults();
// When we ask Realm for all of the Person instances, return the mock RealmResults
when(mockRealm.where(Person.class).findAll()).thenReturn(people);
// When a between query is performed with any string as the field and any int as the
// value, then return the personQuery itself
when(personQuery.between(anyString(), anyInt(), anyInt())).thenReturn(personQuery);
// When a beginsWith clause is performed with any string field and any string value
// return the same person query
when(personQuery.beginsWith(anyString(), anyString())).thenReturn(personQuery);
// When we ask the RealmQuery for all of the Person objects, return the mock RealmResults
when(personQuery.findAll()).thenReturn(people);
// The for(...) loop in Java needs an iterator, so we're giving it one that has items,
// since the mock RealmResults does not provide an implementation. Therefore, anytime
// anyone asks for the RealmResults Iterator, give them a functioning iterator from the
// ArrayList of Persons we created above. This will allow the loop to execute.
when(people.iterator()).thenReturn(personList.iterator());
// Return the size of the mock list.
when(people.size()).thenReturn(personList.size());
this.mockRealm = mockRealm;
this.people = people;
}
#Test
public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() {
doCallRealMethod().when(mockRealm).executeTransaction(Mockito.any(Realm.Transaction.class));
// Create activity
ExampleActivity activity = Robolectric.buildActivity(ExampleActivity.class).create().start().resume().visible().get();
assertThat(activity.getTitle().toString(), is("Unit Test Example"));
// Verify that two Realm.getInstance() calls took place.
verifyStatic(times(2));
Realm.getDefaultInstance();
// verify that we have four begin and commit transaction calls
// Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649
//verify(mockRealm, times(4)).executeTransaction(Mockito.any(Realm.Transaction.class));
// Click the clean up button
activity.findViewById(R.id.clean_up).performClick();
// Verify that begin and commit transaction were called (been called a total of 5 times now)
// Do not verify partial mock invocation count: https://github.com/jayway/powermock/issues/649
//verify(mockRealm, times(5)).executeTransaction(Mockito.any(Realm.Transaction.class));
// Verify that we queried for Person instances five times in this run (2 in basicCrud(),
// 2 in complexQuery() and 1 in the button click)
verify(mockRealm, times(5)).where(Person.class);
// Verify that the delete method was called. Delete is also called in the start of the
// activity to ensure we start with a clean db.
verify(mockRealm, times(2)).delete(Person.class);
// Call the destroy method so we can verify that the .close() method was called (below)
activity.onDestroy();
// Verify that the realm got closed 2 separate times. Once in the AsyncTask, once
// in onDestroy
verify(mockRealm, times(2)).close();
}

Categories

Resources