Realm Unit Testing - android

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
}

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();
}
}

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();
}

Realm - Cannot query objects inside an Activity/Fragment when these Objects were created on the Test Class #Before method

When trying to create mock data in the #Before method of a JUnity4 test case, I'm not able to query the created data using Realm inside the Activity that was being tested.
The issue is that JUnity tests start the activity Before the #Before method runs.
This means that the data created on the test case wasn't available when the Activity started.
Solution:
Tell the test runner to not start the Activity before the tests run.
#Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class, false, false); // NOTE THE FALSES
Start the activity manually after creating the data you want.
#Before
public void before() {
// This must be the same config as the one being used by your app in the test.
final RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext())
.name(TaskerApplication.REALM_FILE)
.deleteRealmIfMigrationNeeded()
.schemaVersion(0)
.build();
realm = Realm.getInstance(configuration);
realm.beginTransaction();
createdObject = realm.copyToRealm(new AnyRealmObject());
realm.commitTransaction();
// Launch the Activity manually
activityRule.launchActivity(new Intent(Intent.ACTION_MAIN));
// Object will be available when queried from the Activity.
}

Mocking SQLite-Database while testing Activity with Robolectric

In the past few days I started playing around with roboguice, robolectric and mockito. I have a small Android-application with a login-screen containing an AutoCompleteTextView for faster entering the username. The usernames for the AutoCompleteTextView are stored in a sqlite-database.
public class MainActivity extends RoboActivity implements View.OnClickListener {
#InjectView(R.id.startScreen_Login_Button) private Button loginButton;
#InjectView(R.id.startScreen_Cancel_Button) private Button cancelButton;
#InjectView(R.id.startScreen_forgotPwd_TextView) private TextView forgotPWTextView;
#InjectView(R.id.startScreen_Username_AutoCompleteTextView) private AutoCompleteTextView loginUsernameAutoCompleteTextView;
#InjectView(R.id.startScreen_Password_EditText) private EditText loginPasswordEditText;
#Inject private SharedPreferences sharedPreferences;
#Inject SQLiteDBAdapter dbAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
loginButton.setOnClickListener(this);
cancelButton.setOnClickListener(this);
forgotPWTextView.setOnClickListener(this);
// Creating List for startScreen_Username_AutoCompleteTextView
List<User> userList = dbAdapter.getUserList();
ListIterator<User> it = userList.listIterator();
List<String> userStringList = new ArrayList<String>();
User user;
while (it.hasNext()) {
user = it.next();
userStringList.add(user.getName());
}
loginUsernameAutoCompleteTextView.setAdapter(new ArrayAdapter<String>(this, R.layout.select_page_row, userStringList));
}
...
}
I want to test MainActivity using robolectric, trying to mock the database with mockito.
This is my test-class:
#RunWith(CustomRobolectricTestRunner.class)
public class MainActivityTest {
#Mock
SQLiteDBAdapter dbAdapter;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void shouldHaveApplicationName() throws Exception {
String appName = new MainActivity().getResources().getString(R.string.app_name);
assertThat(appName, equalTo("OperationReport"));
}
#Test
public void testButtonsVisible()
{
MainActivity mainActivity = new MainActivity();
mainActivity.onCreate(null);
}
}
Calling mainActivity.onCreate(null); is starting the error-cascade, ending in line Cursor cursor = db.rawQuery(SQL_QUERY, null); of my getUserList-method in my SQLiteDBAdapter:
public List<User> getUserList() {
SQLiteDatabase db = getReadableDatabase();
List<User> userList = new ArrayList<User>();
String SQL_QUERY = "SELECT * FROM User;";
Cursor cursor = db.rawQuery(SQL_QUERY, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
User user = new User();
user.setUserUUID(cursor.getString(0));
user.setName(cursor.getString(1));
user.setPassword(cursor.getString(2));
user.setDateOfBirth(cursor.getString(3));
user.setStaffNumber(cursor.getString(4));
user.setActive(cursor.getInt(5));
user.setUserClass(cursor.getInt(6));
userList.add(user);
cursor.moveToNext();
}
cursor.close();
db.close();
return userList;
}
I read, that a Mock is returning empty stubs of void-methods, and returns null on any other method. As I am mocking the SQLiteDBAdapter-class I am expecting that calling getUserList on my mocked SQLiteDBAdapter returns null. It is not quite clear to me, why he is accessing the original method. I guess it is still using the original SQLiteDBAdapter and not the Mock. What do I have to do to fix this, and how is it working? I ran out of ideas, so any help is appreciated.
Mocking a database to test a DAO makes no sense to me at all. What are you testing? The database. Why eliminate it?
Mocking the database makes sense once you have all your DAO tests passing and it's time to test the service that users it to fulfill a unit of work. You've already tested the DAO and the database, and your service unit test need not be an integration test. By all means mock away in that case.
I don't know a lot about what you're mocking, but when I mock it's for interfaces of my making. The mock provides a stand-in implementation for the interface-typed reference that my client/test is using.
If you're trying to mock a concrete class I'd recommend wrapping that adapter inside an interface-based implementation. It'll be a better abstraction and you'll have an easier time mocking your interface.

Android Techniques for Mock Data Source in Activity Unit testing

I'm fairy new to unit testing and I've been learning how to use the jUnit framework for android(using ActivityInstrumentationTestCase2) but I'm having trouble working out how to I inject a mock data source into and activity, example:
In the activiy I have this
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState,R.layout.market_screen);
ListView products = (ListView)findViewById(R.id.product_list);
MarketsAdapter adapter = new MarketsAdapter(this, new ProductDataSource());
products.setAdapter(adapter);
}
I currently pass a ProductDataSource into the adapter which connects to a webservice to pull in products for the adapter. In my tests I don't want to connect to the webservice. What is the best technique to inject a mock data source into the activity for testing? Should I create the ProductDataSource in an Application instance and then use a MockApplication in my tests to create a mock data source?
Thanks
I solved by doing the following in the test class setUp() method: Grab a reference to the ListView and set the Mock Data Source using setAdapter(MockDataSource). This has to be run on the UI thread using runOnUiThread() method.
mActivity = getActivity();
mDataSource = new FakeDataSource();
mMarketsListView = (ListView)mActivity.findViewById(R.id.product_list);
mActivity.runOnUiThread(
new Runnable() {
public void run() {
mMarketsListView.setAdapter(new MarketsAdapter(mActivity,mDataSource));
} // end of run() method definition
} // end of anonymous Runnable object instantiation
); //
Judging by your resolution, you are more referring to "Mocking" as stubbing out some test data. That's always a great way to more forward with development when you're more concerned with functionality and don't really care about the specifics.
So I'm just providing you with this answer because you said you were new to unit testing. So if you were writing a unit test that was dependent on a ProductsDatasource you could also use a Mocking framework to plug in a "mock" object instead of stubbing out a concrete class. I work more with .Net than Java, but all of my code examples will use JUnit and JMock to describe what I'm talking about.
Mocking works by creating mock "Concrete" objects for interfaces. Remember, an interface is just a contract that says your class will provide the specified methods.
So say you had a ProductsDatasource interface implementation like so:
public interface IProductsDatasource {
public List<Product> getProducts();
}
And a concrete type of:
public class ProductsDatasource implements IProductsDatasource {
private List<Product> mProducts;
public ProductsDatasource(List<Product> products) {
mProducts = products;
}
public List<Product> getProducts() {
return mProducts;
}
}
Now, say you're unit testing something, say TargetAdapter, that takes in a ProductsDatasource. If you create a new ProductsDatasource then you would have a dependency. Your unit test would now depend on the class you are testing, and ProductsDatasource. Maybe you've already tested ProductsDatasource in another suite.
public class TargetAdapter {
private IProductsDatasource mDatasource;
public TargetAdapter(IProductsDatasource datasource) {
mDatasource = datasource;
}
public List<Product> products() {
return mDatasource.getProducts();
}
}
So here is a test case, without mocking, that details what I'm talking about.
#Test
public void TargetAdapterReturnsProducts() {
List<Product> data = new ArrayList<Product>();
data.add(new Product("Sample Product 1"));
data.add(new Product("Sample Product 2"));
data.add(new Product("Sample Product 3"));
TargetAdapter adapter = new TargetAdapter(new ProductsDatasource(data)); // See the dependency
List<Product> products = adapter.products();
Assert.assertNotNull(adapter);
Assert.assertTrue(products.size() == 3);
}
So to test my adapter, I have to create a new adapter AND a new datasource. I don't really care about the datasource, I just need to make sure my adapter does what I intended for it to do. Mocking allows me to test my adapter in isolation by specifying the interface type and configuring how I want it to behave. Now I'm not tied down to a concrete class implementation to test my adapter.
So here is an example, where I use JMock to create a mock datasource:
#Test
public void MockingTest() {
final Mockery context = new Mockery();
final List<Product> mockData = new ArrayList<Product>();
mockData.add(new Product("Sample Product 1"));
mockData.add(new Product("Sample Product 2"));
mockData.add(new Product("Sample Product 3"));
final IProductsDatasource mockDatasource = context.mock(IProductsDatasource.class);
context.checking(new Expectations(){{
oneOf (mockDatasource).getProducts(); will(returnValue(mockData)); // This is where I tell JMock to return my test data when getProducts() is called
}});
TargetAdapter adapter = new TargetAdapter(mockDatasource); // No dependency ;)
List<Product> products = adapter.products();
Assert.assertNotNull(adapter);
Assert.assertTrue(products.size() == 3);
}
Since you stated you were new to unit testing, I wanted to point out the power of Mock objects in unit testing and how you can leverage them to write better code. You can also setup mock objects to make sure your target object calls a method on your mock. I use that a lot where I'm not concerned with the implementation of a method or the result, I just want to make sure my class calls it when it should. Now to make all this work, you have to use interfaces but it's pretty easy to just do a refactor -> extract interface
I ran all this in eclipse before I posted it so the code works if you want to play around with it. Hope this helps!

Categories

Resources