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!
Related
I have an application which displays data (posts) from a web API.
A background service syncs this data at some unknown time and saves it.
When visiting my main activity it loads this data and displays it in a RecyclerView
The loading is handled via a singleton class
I currently test the main activity as follows
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void testDataLoad() {
int postsTotal = DataSingleton.getInstance().getPostsCount();
ViewInteraction empty = onView(withId(R.id.empty_view));
ViewInteraction recycler = onView(withId(R.id.recycler_view));
if (postsTotal == 0) {
empty.check(matches(isDisplayed()));
recycler.check(matches(not(isDisplayed())));
} else {
empty.check(matches(not(isDisplayed())));
recycler.check(matches(isDisplayed()));
recycler.check(new RecyclerViewItemCountAssertion(greaterThan(postsTotal)));
}
}
I know that this can't be the right way to write tests. I want to be able to test both with an empty data set and a non-empty set so that the if-else is two separate tests. The only way I think I can achieve it is to mock the data.
Is there another way?
Can I use Mockito to make the MainActivity use mock data without modifying the production code? Is my only choice to make it inject either real or mocked data providers in place of my singleton?
Is it better to just uninstall and reinstall my app each time so there is no data to start with and then continue with real data testing?
Android Activity are heavyweight and hard to test. Because we don't have control over the constructor, it is hard to swap in test doubles.
The first thing to do is to make sure you are depending on an abstraction of the data-source rather than a concretion. So if you are using a singleton with a getPostsCount() method then extract an interface:
interface DataSourceAbstraction {
int getPostsCount();
}
Make a wrapper class that implements your interface:
class ConcreteDataSource implements DataSourceAbstraction {
#Override
int getPostsCount() {
return DataSingleton.getInstance().getPostsCount();
}
}
And make the Activity depend on that rather than the concrete DataSingleton
DataSourceAbstraction dataSourceAbstraction;
#Override
protected void onCreate(Bundle savedInstanceState) {
super(savedInstanceState);
injectMembers();
}
#VisibleForTesting
void injectMembers() {
dataSourceAbstraction = new ConcreteDataSource();
}
You can now swap in a test double by subclassing and overriding injectMembers that has relaxed visibility. It's a bad idea do this in enterprise development, but there are less options in Android Activities where you don't control the constructor of the class.
You can now write:
DataSourceAbstraction dataSource;
//system under test
MainActivity mainActivity
#Before
public void setUp() {
mockDataSource = Mockito.mock(DataSourceAbstraction.class);
mainActivity = new MainActivity() {
#Override
void injectMembers() {
dataSourceAbstraction = mockDataSource;
}
};
}
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
}
I recently started using Dagger 2 to manage the depency injections of my app. In order to make some classes testable, I started creating many classes that I need to inject. I was able to do so, however, the process to get these new classes injected look a little bit complicaded to me. Lets take an example:
I would like to test, for instance, the method onCreateViewHolder() from my RecyclerViewAdapter. To do so, I've created a Factory that returns the ViewHolder based on the given LayoutType:
public class ViewHolderFactor {
public RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = this.getLayoutInflater(parent.getContext());
View view;
switch (LayoutType.fromInteger(viewType)) {
case SMALL_VERTICAL:
view = inflater.inflate(R.layout.rsc_util_item_small, parent, false);
return new ViewHolder.ItemViewHolder(view);
case LARGE_VERTICAL:
view = inflater.inflate(R.layout.rsc_util_item_large, parent, false);
return new ViewHolder.ItemViewHolder(view);
}
return null;
}
private LayoutInflater getLayoutInflater(Context context) {
return LayoutInflater.from(context);
}
}
By moving the above code to a separated class, I'm able to perform my unit test using:
#RunWith(JUnit4.class)
public class TestViewHolderFactor extends TestCase {
ViewHolderFactor viewHolderFactor;
#Test
public void testGetViewHolder () {
this.viewHolderFactor = Mockito.mock(ViewHolderFactor.class);
ViewGroup viewGroup = Mockito.mock(ViewGroup.class);
Mockito.when(viewHolderFactor.getViewHolder(viewGroup, LayoutType.SMALL_VERTICAL.toInteger())).thenCallRealMethod();
Context context = Mockito.mock(Context.class);
Mockito.when(viewGroup.getContext()).thenReturn(context);
LayoutInflater layoutInflater = Mockito.mock(LayoutInflater.class);
Mockito.when(viewHolderFactor.getLayoutInflater(context)).thenReturn(layoutInflater);
Mockito.when(layoutInflater.inflate(R.layout.rsc_util_item_small, viewGroup, false)).thenReturn(Mockito.mock(View.class));
RecyclerView.ViewHolder result = viewHolderFactor.getViewHolder(viewGroup, LayoutType.SMALL_VERTICAL.toInteger());
assertNotNull(result);
}
}
Problem is: now, to make the app work, I will also have to inject the outter class that holds an instance to the factor (something that I wasn't doing before creating ViewHolderFactor). At end, I will have a Dagger configuration like that:
#Module
public class ModuleBusiness {
#Provides
public CharacterUIService provideCharacterService(Picasso picasso, NotificationUtil notificationUtil, ViewHolderFactor viewHolderFactor) {
return new CharacterUIService(picasso, notificationUtil, viewHolderFactor);
}
}
Where CharacterUIService is the class that creates a new instance of the RecyclerViewAdapter that contains the ViewHolderFactory.
public class
private ViewHolderFactor
#Inject
public CharacterUIService(ViewHolderFactor viewHolderFactor) {
this.mViewHolderFactor = viewHolderFactor;
}
// ...
}
I need a member from ViewHolderFactor in order to inject it.
My worries regards the need to create new global variables and inscrease the number of parameters being passed in the constructor. Is there a better way to inject these subclasses or can I consider it as a good practice in order to allow Unit Tests?
can I consider it as a good practice in order to allow Unit Tests?
First of all—in my personal opinion—I think that you are overdoing the testing. In the test case that you provided you are basically testing the android framework itself. If you want to test the factory, you should test whether a small or large item is returned, rather than the view being not null.
But yes, yours seems like a reasonable approach to allow for unit testing.
Where CharacterUIService is the class that creates a new instance of the RecyclerViewAdapter
You might want to overthink the role of CharacterUIService. "that creates a new instance" sounds like something that should rather be handled by dagger, since you are already using it.
will also have to inject the outter class that holds an instance to the factory
Why? ViewHolderFactor(sic!) has no dependencies itself. And even if it did, what is stopping you from just creating and injecting it with dagger?
class ViewHolderFactor {
#Inject
ViewHolderFactor() { // allow for constructor injection
}
}
class YourAdapter {
ViewHolderFactor mFactor;
#Inject // allow for constructor injection
YourAdapter (ViewHolderFactor factor) {/**/}
}
// now dagger knows how to create that adapter
And just create let dagger create that adapter?
And generally, if you support constructor injection, then you can remove your #Provides providesSomething() methods. Just remove the whole method. You have an #Inject annotation on the constructor, so make use of that and let dagger write that code for you.
// CharacterUIService can be constructor injected.
#Provides // DAGGER WILL DO THIS FOR YOU
public CharacterUIService provideCharacterService(Picasso picasso, NotificationUtil notificationUtil, ViewHolderFactor viewHolderFactor) {
// REMOVE ALL OF THIS.
return new CharacterUIService(picasso, notificationUtil, viewHolderFactor);
}
So while I think you do a good job by making things testable, you should have again a good look on dagger, since you are obviously mixing many things up. Have a good look on constructor injection and make use of it, it saves you a whole lot of work.
My android app communicate with backend service through REST API . I want to mock out this API to quickly develop the front end.
I am using android volley as client side networking library.
You can use the dependency injection design pattern for this.
Basically you specify an interface that defines a set of methods corresponding to the queries you have in your REST backend, e.g.:
interface DataSupplier {
// Lookup user by ID
User getUser(int id);
// Get all blog posts posted by a specific user.
List<BlogPost> getUsersBlogPosts(int userId);
}
Now in the class where you need to query the backend, you specify an injector. This can be done in multiple ways (e.g. constructor injection, setter injection - see the wiki article for more details). An injector lets you inject an implementation of the dependency into the class that depends on it. Let us assume you use constructor injection. Your class that uses the backend would look like this:
public class DependentClass {
private final DataSupplier mSupplier;
public DependentClass(DataSupplier dataSupplier) {
mSupplier = dataSupplier;
}
// Now you simply call mSupplier whenever you need to query the mock
// (or - later in development - the real) REST service, e.g.:
public void printUserName() {
System.out.println("User name: " + mSupplier.getUser(42).getName());
}
}
Then you create a mock implementation of DataSupplier:
public class MockRestService implements DataSupplier {
#Override
public User getUser(int id) {
// Return a dummy user that matches the given ID
// with 'Alice' as the username.
return new User(id, "Alice");
}
#Override
public List<BlogPost> getUsersBlogPosts(int userId) {
List<BlogPost> result = new ArrayList<BlogPost>();
result.add(new BlogPost("Some Title", "Some body text"));
result.add(new BlogPost("Another Title", "Another body text"));
result.add(new BlogPost("A Third Title", "A third body text"));
return result;
}
}
and use that to instantiate your dependent class:
DepedentClass restClient = new DepedentClass(new MockRestService());
Now you can use restClient as if it was connected to your actual backend. It will simply return dummy objects that you can use to develop your front end.
When you are done with your front end and ready to implement your backend, you do so by creating another implementation of DataSupplier that sets up a connection to your REST backend and queries it for real objects. Let us say you name this implementation RestService. Now you can simply replace the constructor creating the MockRestService with your RestService constructor like so:
DepedentClass restClient = new DepedentClass(new RestService());
And there you have it: by swapping a single constructor call, you can change your front end code from using dummy objects to using real REST-delivered objects.
You could even have a debug flag and create the restClient according to the state of your application (debug or release):
boolean debug = true;
DependentClass restClient = null;
if (debug) {
restClient = new DepedentClass(new MockRestService());
} else {
restClient = new DepedentClass(new RestService());
}
I've recently created RESTMock. It is a library for Mocking REST API's in android tests. It can be used during development though. You would need to set it up following the README on github and create a basic Android Instrumentation test that would start your app and do nothing. This way the app is started with the Mock Server in background.
Example test:
public class SmokeTest {
#Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<MainActivity>(
SplashActivity.class,
true,
false);
#Test
public void smokeTest() throws InterruptedException {
rule.launchActivity(null);
Thread.sleep(10000000);
}
}
I am trying to test a Fragment I've created in Android. I have complete control of the code, so I can change it as I see fit. The issue is that I'm not sure what design pattern I'm missing to make it reasonable.
I am looking for a way to mock objects in Android that are not passed as parameters. This question suggests that anything you might want to mock should be written to be passed as a parameter.
This makes sense for some situations, but I can't figure out how to get it working on Android, where some of this isn't possible. With a Fragment, for example, you're forced to let much of the heavy lifting be done in callback methods. How can I get my mocked objects into the Fragment?
For example, in this ListFragment I need to retrieve an array of things to display to the user. The things I'm displaying need to be retrieved dynamically and added to a custom adapter. It currently looks as follows:
public class MyFragment extends ListFragment {
private List<ListItem> mList;
void setListValues(List<ListItem> values) {
this.mList = values;
}
List<ListItem> getListValues() {
return this.mList;
}
#Override
public void onCreateView(LayoutInflater i, ViewGroup vg, Bundle b) {
// blah blah blah
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
this.setListValues(ListFactory.getListOfDynamicValues());
CustomAdapter adapter = new CustomAdapter(
getActivity(),
R.layout.row_layout,
this.getListValues());
this.setListAdapter(adapter);
}
}
I'm trying to do this using Mockito and Robolectric.
This is the beginning of my robolectric test case:
public class MyFragmentTest {
private MyFragment fragment;
#Before
public void setup() {
ListItem item1 = mock(ListItem.class);
ListItem item2 = mock(ListItem.class);
when(item1.getValue()).thenReturn("known value 1");
when(item2.getValue()).thenReturn("known value 2");
List<ListItem> mockList = new ArrayList<ListItem>();
mockList.add(item1);
mockList.add(item2);
MyFragment real = new MyFragment();
this.fragment = spy(real);
when(this.fragment.getValueList()).thenReturn(mockList);
startFragment();
}
}
This feels so very wrong. This section from the mockito api points out that you shouldn't have to do partial mocks like this very frequently unless you're dealing with legacy code.
Further, I'm not actually able to mock out the CustomAdapter class using this approach.
What is the right way to do this sort of thing? Am I structuring things incorrectly in my Fragment classes? I suppose I might be able to add a bunch of package-private setters, but this still doesn't feel right.
Can someone shed some light on this? I'm happy to do rewrites, I just want to know some good patterns for dealing with the state in my Fragments and how I can make them testable.
I ended up creating my own solution to this. My approach was to add another level of indirection to each my calls that create or set an object.
First, let me point out that I couldn't actually get Mockito to work reliably with Fragment or Activity objects. It was somewhat hit or miss, but especially with trying to create Mockito Spy objects, some lifecycle methods appeared to not be called. I think this is related to gotcha number 2 shown here. Perhaps this is due to the ways that Android uses reflection to recreate and instantiate activities and fragments? Note that I was NOT incorrectly holding onto the reference, as it warns of, but interacting only with the Spy, as indicated.
So, I wasn't able to mock Android objects that required lifecycle methods be invoked by the framework.
My solution was to create to more types of methods in my Activity and Fragment methods. These methods are:
getters (getX()) that return the field named X.
retrievers (retrieveX()) that do some sort of work to get an object.
creators (createMyFragment()) that create objects by calling new. Similar to the retrievers.
Getters have whatever visibility you need. Mine are usually public or private.
Retrievers and creators are package private or protected, allowing you to override them in your test packages but not making them generally available. The idea behind these methods is that you can subclass your regular objects with stub objects and inject in known values during testing. You could also just mock out those methods if Mockito mocks/spies are working for you.
Taken in toto, the test would look something like the following.
Here is the fragment from my original question, modified to use the above approach. This is in the normal project:
package org.myexample.fragments
// imports
public class MyFragment extends ListFragment {
private List<ListItem> mList;
void setListValues(List<ListItem> values) {
this.mList = values;
}
List<ListItem> getListValues() {
return this.mList;
}
#Override
public void onCreateView(LayoutInflater i, ViewGroup vg, Bundle b) {
// blah blah blah
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
this.setListValues(this.retrieveListItems());
CustomAdapter adapter = this.createCustomAdapter();
this.setListAdapter(adapter);
}
List<ListItem> retrieveListItems() {
List<Item> result = ListFactory.getListOfDynamicValues();
return result;
}
CustomAdapter createCustomAdapter() {
CustomAdapter result = new CustomAdapter(
this.getActivity();
R.layout.row_layout,
this.getListValues());
return result;
}
}
When I test this object, I want to be able to control what gets passed around. My first thought was to use a Spy, replacing the return values of retrieveListItems() and createCustomAdapter() with my known values. However, like I said above, I wasn't able to get Mockito spies to behave when working with fragments. (Especially ListFragments--I had mixed success with other types, but don't trust it.) So, we are going to subclass this object. In the test project, I have the following. Note that your method visibility in your real class must allow subclasses to override, so it needs to be package private and in the same package or protected. Note that I am overriding the retriever and creator, returning instead static variables that my tests will set.
package org.myexample.fragments
// imports
public class MyFragmentStub extends MyFragment {
public static List<ListItem> LIST = null;
public static CustomAdapter ADAPTER = null;
/**
* Resets the state for the stub object. This should be called
* in the teardown methods of your test classes using this object.
*/
public static void resetState() {
LIST = null;
ADAPTER = null;
}
#Override
List<ListItem> retrieveListItems() {
return LIST_ITEMS;
}
#Override
CustomAdapter createCustomAdapter() {
return CUSTOM_ADAPTER;
}
}
In the same package in my test project I have the actual test of the fragment. Note that while I'm using Robolectric, this should work with whatever test framework you're using. The #Before annotation becomes less useful, as you need to update your static state for individual tests.
package org.myexample.fragments
// imports
#RunWith(RobolectricTestRunner.class)
public class MyFragmentTest {
public MyFragment fragment;
public Activity activity;
#After
public void after() {
// Very important to reset the state of the object under test,
// as otherwise your tests will affect each other.
MyFragmentStub.resetState();
}
private void setupState(List<ListItem> testList, CustomAdapter adapter) {
// Set the state you want the fragment to use.
MyFragmentStub.LIST = testList;
MyFragmentStub.ADAPTER = adapter;
MyFragmentStub stub = new MyFragmentStub();
// Start and attach the fragment using Robolectric.
// This method doesn't call visible() on the activity, though so
// you'll have to do that yourself.
FragmentTestUtil.startFragment(stub);
Robolectric.ActivityController.of(stub.getActivity()).visible();
this.fragment = stub;
this.activity = stub.getActivity();
}
#Test
public void dummyTestWithKnownValues() {
// This is a test that does nothing other than show you how to use
// the stub.
// Create whatever known values you want to test with.
List<ListItem> list = new ArrayList<ListItem>();
CustomAdapter adapter = mock(CustomAdapter.class);
this.setupState(list, adapter);
// android fest assertions
assertThat(this.fragment).isNotNull();
}
}
This is definitely more verbose than using a mocking framework. However, it works even with Android's life cycle. If I'm testing an Activity, I'll also often include a static boolean BUILD_FRAGMENTS variable. If true, I'll go call through to super in the appropriate methods or return a known fragment as appropriate. In this way I'm able to inject my test objects and play nice with the Android life cycle.