Mock objects on Android with Borachio: Part 1

Now that Mockito has Android support, I am no longer supporting Borachio/ScalaMock on Android.

For a complete worked example of using Borachio on Android with RoboGuice for Dependency Injection, go here.

One of my biggest frustrations with writing code for Android has been the fact that none of the current Java mocking frameworks work on Android’s Dalvik VM. I recently released Borachio a native Scala mocking framework which does work on Android.

Because Borachio is written in Scala, you’ll need to write your tests in Scala. But it can be used to test code written in Java.

This post demonstrates how to get basic mocking working. Things get more complicated when you try to mock bits of Android itself, but I’ll cover that in a subsequent article.

This is an Android version of the example in Martin Fowler’s article Mocks Aren’t Stubs. The code is checked into GitHub here. You’ll need to have the Android SDK and Scala 2.8 installed to run this code.

We’re going to build a (very) simple ordering system. Orders will succeed if there’s enough inventory in our warehouse and fail if not. Let’s start by creating a very simple little Android application for us to test:

Create a new project with: android create project -p WarehouseManager -t android-8 -p warehousemanager -k com.example.warehousemanager -a WarehouseManager The core abstraction is a warehouse, represented by a Warehouse interface: package com.example.warehousemanager; public interface Warehouse { boolean hasInventory(String product, int quantity); void remove(String product, int quantity); } And here’s a very simple concrete implementation of Warehouse : package com.example.warehousemanager; import java.util.HashMap; public class RealWarehouse implements Warehouse { public RealWarehouse() { products = new HashMap(); products.put("Talisker", 5); products.put("Lagavulin", 2); } public boolean hasInventory(String product, int quantity) { return inStock(product) >= quantity; } public void remove(String product, int quantity) { products.put(product, inStock(product) - quantity); } private int inStock(String product) { Integer quantity = products.get(product); return quantity == null ? 0 : quantity; } private HashMap products; } We remove things from the warehouse by placing an Order : package com.example.warehousemanager; public class Order { public Order(String product, int quantity) { this.product = product; this.quantity = quantity; } public void fill(Warehouse warehouse) { if (warehouse.hasInventory(product, quantity)) { warehouse.remove(product, quantity); filled = true; } } public boolean isFilled() { return filled; } private boolean filled = false; private String product; private int quantity; } We’ll need a UI to allow us to make orders, so modify main.xml to look like this: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Product:" /> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/product" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Quantity:" /> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/quantity" /> <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Place order" android:onClick="placeOrder" /> </LinearLayout> And finally, here’s the implementation of WarehouseManager : package com.example.warehousemanager; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.Toast; public class WarehouseManager extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); productEditText = (EditText)findViewById(R.id.product); quantityEditText = (EditText)findViewById(R.id.quantity); } public void placeOrder(View view) { String product = productEditText.getText().toString(); int quantity = Integer.parseInt(quantityEditText.getText().toString()); Order order = new Order(product, quantity); order.fill(warehouse); String message = order.isFilled() ? "Success" : "Failure"; Toast toast = Toast.makeText(this, message, Toast.LENGTH_SHORT); toast.show(); } private Warehouse warehouse = new RealWarehouse(); private EditText productEditText; private EditText quantityEditText; }

You should now have a little Android application that can be compiled and installed with ant install . Here’s what it looks like:

So, now that we’ve got something to test, let’s create a test project to test it:

Create a test project with: android create test-project -p test -m .. -n WarehouseManagerTest Next, we’ll convert this to a Scala project, as described here. Add scala.dir and proguard.dir to local.properties . Here’s what I added to mine (you’ll need to change the paths to match your local installation): scala.dir=/opt/local/share/scala-2.8 proguard.dir=/Users/paul/android-sdk-mac_86/tools/proguard/ Copy build-scala.xml into the root of the test project and add the following to build.xml : <import file="build-scala.xml" /> <!-- Converts this project's .class files into .dex files --> <target name="-dex" depends="compile, scala-compile, scala-shrink"> <scala-dex-helper /> </target> Delete the proguard.cfg file and copy the configs directory into the test project. Add the following to the bottom of both default-debug.cfg and default-release.cfg (to ensure that ProGuard doesn’t discard our test classes: -keep public class * implements junit.framework.Test { public void test*(); } Copy the Borachio JAR to the libs directory. Finally, we can write our tests, which create mock instances of the Warehouse interface: package com.example.warehousemanager; import junit.framework.TestCase import com.borachio.junit3.MockFactory class OrderTest extends TestCase with MockFactory { def testInStock() { withExpectations { val mockWarehouse = mock[Warehouse] inSequence { mockWarehouse expects 'hasInventory withArguments ("Talisker", 50) returning true once; mockWarehouse expects 'remove withArguments ("Talisker", 50) once } val order = new Order("Talisker", 50) order.fill(mockWarehouse) assert(order.isFilled) } } def testOutOfStock() { withExpectations { val mockWarehouse = mock[Warehouse] mockWarehouse expects 'hasInventory returns false once val order = new Order("Talisker", 50) order.fill(mockWarehouse) assert(!order.isFilled) } } } Run the tests with: ant run-tests

In part 2, we’ll look at some of the challenges of mocking Android components.

Updated 2011-04-15

Updated to Borachio 0.6.