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();
}
Related
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();
}
}
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.
I have just started unit testing in android. I cannot unit test android internal method to notify that data is changed (notifyDataSetChanged)and I am getting null pointer exception on this point. I am using mockito and power moockito to mock different objects.
public void RecyclerView(String value,Bitmap image) {
RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainActivity.this);
recyclerView.setAdapter(adapter);// set adapter on recyclerview
adapter.notifyDataSetChanged();// Notify the adapter
}
My unit test is:
#RunWith(PowerMockRunner.class)
#PrepareForTest({MainActivity.class})
public class MainActivityTest {
#Mock
Bitmap bitmap;
#Mock
RecyclerView recycleview;
#InjectMocks
Activity activity;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mainactivity = new MainActivity();
}
#Test
public void PopulateRecyleViewTest() {
try {
final RecyclerViewAdapter abc = PowerMockito.mock(RecyclerViewAdapter.class);
PowerMockito.whenNew(RecyclerViewAdapter.class).withArguments(mainactivity).thenReturn(abc);
doNothing().when(abc).notifyDataSetChanged(); //do nothing getting exception here
mainactivity.recyclerView = recycleview;
mainactivity.PopulateRecyleView("", bitmap);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Is there any way to unit test this method so that notifydatasetchanged() do not give NPE(Null Pointer Exception)? I have read that power mockito is used to unit test final method but it does not seem to be unit testing notifyDataSetChanged which is final method. Any help would be appreciated.
Acc to Android documentation - "If your unit test has no dependencies or only has simple dependencies on Android, you should run your test on a local development machine"
Mockito/PowerMockito runs on your JVM.
#InjectMocks
Activity activity;
It is not straight forward to mock the activity that is why we have Instrumented Unit Tests. I see lot of problem's in the above test case. I think you should write instrumentation case in your case.
Use Mockito/PowerMockito where you have more of Java objects, like your presentation class (in MVP architecture) or any business logic.
Please checkout android documentation on testing here.
This will give you proper picture of how and what should be your approach for writing unit test cases in Android.
With Mockito 2.0 and above you can mock final classes and methods by adding a file. Check out this document: Mock the unmockable: opt-in mocking of final classes/methods
Basically it states that you have to create the file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker then add the single line:
mock-maker-inline
After that you should be able to mock a final class or method. I had the same issue you had and this worked for me.
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
}
What can I do to get some test coverage on ActiveAndroid's ContentProvider in Robolectric? This simple test fails.
The model:
#Table(name = "Things")
public class Thing extends Model {
public Thing() {
super();
}
}
The test:
#RunWith(RobolectricTestRunner.class)
public class ContentProviderTest {
#Test
public void itShouldQuery() throws Exception {
new Thing().save();
ContentResolver cr = new MainActivity().getContentResolver();
assertNotNull(
cr.query(Uri.parse("content://org.example/things"),
null, null, null, null));
}
}
The resulting stack trace:
java.lang.NullPointerException: null
at com.activeandroid.Cache.getTableInfo(Unknown Source)
at com.activeandroid.Model.<init>(Unknown Source)
at org.example.Thing.<init>(Thing.java:9)
at org.example.ProviderTest.itShouldQuery(ProviderTest.java:25)
The application context should be ok. By default, Robolectric creates the application that appears in the manifest, which in this case is com.activeandroid.Application.
So, I'm puzzled why the tableInfo in Cache is not initialized. Normal application execution works fine.
To automatically scan ActiveAndroid Models automatically during maven unit tests requires a simple change to ModelInfo.scanForModel.
In that method, there is a "Robolectric fallback" which detects and scans paths containing "bin". This handles Model classes in Eclipse projects.
Maven compiles to target/classes. An additional check for "classes" in scan paths in ModelInfo does the trick.
Adding an ActiveAndroid pull request for this soon.