Archive for March, 2011

Mock objects on Android with Borachio: Part 3

As we saw in part 2 of this series, mocking Android’s PowerManager service directly is impossible. But there is an alternative approach that gives us something close enough. This article describes that approach.

The code of the application described here is checked into GitHub.

Given that we can’t mock PowerManager directly, instead we’re going to create an interface that we can mock:

public interface PowerControl
{
    void disablePowerOff();
    void enablePowerOff();
}

Together with an implementation which will be used in production code:

public class PowerControlImpl implements PowerControl
{
    public PowerControlImpl(Context context) {
        PowerManager powerManager = (PowerManager)
            context.getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(
            PowerManager.FULL_WAKE_LOCK, "PowerControl");
    }

    public void disablePowerOff() {
        wakeLock.acquire();
    }

    public void enablePowerOff() {
        wakeLock.release();
    }

    private PowerManager.WakeLock wakeLock;
}

We won’t be able to test this implementation, but hopefully it’s so simple that (as Hoare puts it) it obviously contains no deficiencies (as opposed to contains no obvious deficiencies).

But we do now have something that we can mock, so we can test that the code that calls it does so correctly.

The first challenge we’re going to have to overcome is how to inject a PowerControl implementation (the real one or the mock) into the code under test. We could use a dependency injection framework like RoboGuice, but for the purposes of this article I’m going to keep things simple and use a custom Application class which implements a getPowerControl method:

public class PowerControlApplication extends Application
{
    public void onCreate() {
        powerControl = new PowerControlImpl(this);
    }

    public PowerControl getPowerControl() {
        return powerControl;
    }

    protected PowerControl powerControl;
}

Our activity calls this during onCreate:

public class PowerActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        PowerControlApplication app =
            (PowerControlApplication)getApplication();
        powerControl = app.getPowerControl();
    }

    public void startImportant(View button) {
        powerControl.disablePowerOff();
    }

    public void stopImportant(View button) {
        powerControl.enablePowerOff();
    }

    private PowerControl powerControl;
}

We can now write a test to verify that startImportant calls disablePowerOff:

class PowerActivityTest
  extends ActivityUnitTestCase[PowerActivity](classOf[PowerActivity])
  with MockFactory {

  val startIntent = new Intent(Intent.ACTION_MAIN)

  def testStartImportant {
    val mockPowerControl = mock[PowerControl]
    val application = new PowerControlApplication {
      powerControl = mockPowerControl
    }
    setApplication(application)
    startActivity(startIntent, null, null)

    withExpectations {
      mockPowerControl expects 'disablePowerOff once

      getActivity.startImportant(null)
    }
  }
}

Our test first creates a mock PowerControl object:

    val mockPowerControl = mock[PowerControl]

And then creates an application object that returns this mock instead of a “real” PowerControl instance:

    val application = new PowerControlApplication {
      powerControl = mockPowerControl
    }

We tell Android’s test framework to use this application object by calling setApplication:

    setApplication(application)

Finally, we set our expectation (that disablePowerOff is called once) and call startImportant:

    mockPowerControl expects 'disablePowerOff once

    getActivity.startImportant(null)

Updated 2011-04-15

Updated to Borachio 0.6.

Mock objects on Android with Borachio: Part 2

In part 1 of this series, I showed how to mock an interface that we created ourselves under Android. That’s useful, but mocking really pays dividends when mocking OS services—doing so allows us to test our code in isolation, verify that it interacts with the OS correctly and that it handles errors properly.

But there’s a wrinkle. Android is the most test-hostile environment I’ve ever had the misfortune to find myself working in. I wonder sometimes if its designers deliberately designed it to make testing as difficult as they possibly could. It can be done, and I’ll show how in part 3, but if you’ll forgive me a digression, in this article I’m going to try the simple, “obvious” solution and demonstrate why it doesn’t work.

I’m going to try to write a simple test of an application that uses Android’s PowerManager service. PowerManager allows us to control when the device switches on or off. If we’re about to start some critical operation that must complete without the device switching off, we can obtain a WakeLock, which is what this sample app does.

The code is checked into GitHub. Here’s the code that we want to test:

package com.paulbutcher.powercontrol;

import android.app.Activity;
import android.os.Bundle;
import android.os.PowerManager;
import android.view.View;

public class PowerControl extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    public void startImportant(View button) {
        PowerManager powerManager =
            (PowerManager)getSystemService(POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(
            PowerManager.FULL_WAKE_LOCK, "PowerControl");
        wakeLock.acquire();
    }

    public void stopImportant(View button) {
        wakeLock.release();
    }

    private PowerManager.WakeLock wakeLock;
}

Let’s try to write a test that verifies that startImportant calls PowerManager.newWakeLock. Our first task is going to be working out how to inject a mock PowerManager into the code under test.

That code obtains its PowerManager instance by calling Context.getSystemService. Happily, Android provides MockContext and ActivityUnitTestCase.setActivityContext, so we should be all set.

Before we get carried away, let’s just verify that we can get a test using a MockContext to run at all:

  def testAttempt1 {
    val mockContext = new MockContext;
    setActivityContext(mockContext)
    startActivity(startIntent, null, null)
  }

Let’s see what happens when we run that:

Failure in testAttempt1:
junit.framework.AssertionFailedError
  at android.test.ActivityUnitTestCase.startActivity(ActivityUnitTestCase.java:148)

Hmm—apparently this isn’t going to be as easy as we hoped.

The problem is that startActivity calls our MockContext. Specifically it calls getSystemService("layout_inflater") which fails because MockContext‘s methods are non-functional and throw UnsupportedOperationException.

It turns out that what Android means by “mock” isn’t what the rest of the world means. As Martin Fowler says in Mocks Aren’t Stubs, mocks are:

objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

Never mind—there is another way. Android’s ContextWrapper allows us to wrap an existing context, only changing those bits of functionality we’re interested in for the purposes of our test:

  def testAttempt2 {
    val testContext =
        new ContextWrapper(getInstrumentation.getTargetContext);
    setActivityContext(testContext)
    startActivity(startIntent, null, null)
  }

That works, so now we just need to modify it to return a mock PowerManager when getSystemService is called:

  def testAttempt3 {
    val mockPowerManager = mock[PowerManager]
    val testContext =
      new ContextWrapper(getInstrumentation.getTargetContext) {
        override def getSystemService(name: String) = name match {
          case "power" => mockPowerManager
          case _ => super.getSystemService(name)
        }
      }
    setActivityContext(testContext)
    startActivity(startIntent, null, null)
  }

Which looks great, right up until we run it:

Error in testAttempt3:
java.lang.IllegalArgumentException: android.os.PowerManager is not an interface

Borachio, in common with most other mocking frameworks, can only mock interfaces, and PowerManager is a class, not an interface. There are mocking frameworks that can mock classes, for example PowerMock, but they rely on code generation, which Android’s Dalvik VM doesn’t (yet) support. So that’s not going to be any help 😦

There’s one final thing we can try. As well as mocking interfaces, Borachio can also mock functions. So we can derive from PowerManager and just mock the single method we’re interested in like this:

  def testAttempt4 {
    val mockNewWakeLock =
      mockFunction[Int, String, PowerManager#WakeLock]
    val mockPowerManager = new PowerManager {
      override def newWakeLock(flags: Int, tag: String) =
        mockNewWakeLock(flags, tag)
    }
    val testContext =
      new ContextWrapper(getInstrumentation.getTargetContext) {
        override def getSystemService(name: String) = name match {
          case "power" => mockPowerManager
          case _ => super.getSystemService(name)
        }
      }
    setActivityContext(testContext)
    startActivity(startIntent, null, null)
  }

But, when we compile this, we get:

PowerControlTest.scala:53: error: constructor PowerManager cannot be accessed in anonymous class $anon
    val mockPowerManager = new PowerManager {
                               ^

PowerManager‘s constructor is private 😦

And at this point, we’re out of easy options. We can’t play the same trick with PowerManager as we played with Context, as there’s no PowerManagerWrapper or similar.

We’re not beaten yet—I’ll show how to get around this problem in part 3 of this series. Small wonder, however, that most of the Android code I’ve seen has virtually no tests!

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:

  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 Warehouse interface:

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

    <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>
  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 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)
        }
      }
    }
  7. 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.