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 ProductDataSo开发者_如何学编程urce 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!
精彩评论