Hot questions for Using Mockito in realm
Question:
Hello I have been ran into a problem when trying to write a JUnit test case and am relatively new to Mockito.
I have a function of a class that I am mocking, this function happens to be of a void return type. When calling this function from my mocked class it is my understanding (and debugging experience) that it does NOT call the original function. In order to overcome this I have attempted to use a "when" with a "thenCallRealMethod()".
when(instance.voidFunction()).thenCallRealMethod();
The "voidFunction" is full of logic that I do NOT want to fire. I have extracted these into when statements to avoid that. I have read that I should use the format of doReturn().when().voidFunction()
, however doing this does not call the real method.
It was also my understanding that I could not use a Spy here, due to the fact that I do not want the voidFunction() called before the "when" statements. Any help is appreciated I apologize if this is a very easy solution as my understanding of mockito isn't very great despite reading quite a bit. Thanks!
Answer:
The when
syntax won't work with a void method (it won't fit within the when
), and doReturn
doesn't apply when there's no return value. doCallRealMethod
is likely the answer you want.
doCallRealMethod().when(instance).voidFunction();
Bear in mind that when calling a real method on a mock, you may not get very realistic behavior, because unlike with spies mocked objects will skip all constructor and initializer calls including those to set fields. That means that if your method uses any instance state at all, it is unlikely to work as a mock with doCallRealMethod
or thenCallRealMethod
. With a spy, you can create a real instance of your class, and then the Mockito.spy
method will copy that instance state over to make for a more realistic interaction.
Question:
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"
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)
Answer:
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).