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

  1. Is this the correct way of testing Realm?

  2. How can I test data that is in the app's sandbox, can that data only be tested by UI/Instrumentation tests?

  3. 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)

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).