Menu

Mock objects on Android with Borachio: Part 1

In the next few “Technical Wizardry” articles, we’re going to look at the approach we’ve developed to help with testing Android code (first published 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:

  1. Create a new project with:
    android create project -p WarehouseManager -t android-8
        -p warehousemanager -k com.example.warehousemanager
        -a WarehouseManager
  2. The core abstraction is a warehouse, represented by a Warehouseinterface:
    package com.example.warehousemanager;
    
    public interface Warehouse {
        boolean hasInventory(String product, int quantity);
        void remove(String product, int quantity);
    }
  3. 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;
    }
  4. 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;
    }
  5. We’ll need a UI to allow us to make orders, so modify main.xmlto look like this:
    
    
  6. 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:

Screenshot

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

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

  2. 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/
  3. Copy build-scala.xml into the root of the test project and add the following to build.xml:
    
    
  4. 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*(); }
  5. Copy the Borachio JAR to the libs directory.
  6. Finally, we can write our tests, which create mock instances of the Warehouseinterface:
    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)
        }
      }
    }
  7. Run the tests with:
    ant run-tests

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

Get Involved

Get exclusive access to pre-release betas and talk to other SwiftKey fans.

VIP Community

@swiftkey