Archive for July, 2011

"Power" mocking in Scala with Borachio

The work described in this post has now been released in ScalaMock.

Over the last few weeks, I’ve been working on major enhancements for Borachio (my native mocking library for Scala) to add facilities similar to those provided by PowerMock for Java. As well as interfaces and functions, it can now mock:

  • Classes
  • Final classes and classes with final methods
  • Classes with private constructors
  • Singleton (companion) objects
  • Object creation (i.e. new)

And, as an extra bonus, mocks are now typesafe :-).

It’s not completely finished yet, but it is close enough that it’s worth soliciting feedback. The code is available on GitHub and a snapshot is available on scala-tools.

Warning: Although this is getting close to “done”, the details may change slightly before final release.

So what does this look like in action? I’m going to demonstrate with a slightly modified version of Martin Fowler’s “warehouse” example from Mocks Aren’t Stubs. This example is available on GitHub. First, here’s a Warehouse object (a Scala singleton object):

object Warehouse {
  val products = Map(("Talisker" -> 5), ("Lagavulin" -> 2))

  def hasInventory(product: String, quantity: Int) =
    inStock(product) >= quantity

  def remove(product: String, quantity: Int) =
    products += (product -> (inStock(product) - quantity))

  def inStock(product: String) = products.getOrElse(product, 0)
}

And here’s an Order class that removes things from the warehouse:

class Order(product: String, quantity: Int) {
  var filled = false

  def fill() {
    if (Warehouse.hasInventory(product, quantity)) {
      Warehouse.remove(product, quantity)
      filled = true
    }
  }
}

object Order {
  def apply(product: String, quantity: Int) =
    new Order(product, quantity)

  def largestPossible(product: String) = {
    val quantity = Warehouse.inStock(product)
    new Order(product, quantity)
  }
}

First, let’s see how we can go about testing the Order class by mocking the Warehouse object:

test("Enough stock") {
  val mockWarehouse = mockObject(Warehouse)

  mockWarehouse.expects.hasInventory("Talisker", 50) returning true
  mockWarehouse.expects.remove("Talisker", 50)

  val order = Order("Talisker", 50)
  order.fill

  assert(order.filled)
}

The “magic” happens in the mockObject call:

  val mockWarehouse = mockObject(Warehouse)

This returns a mock version of the Warehouse object upon which expectations can be set. From that point onwards, the code should be pretty self-explanatory.

Another example, this time of mocking object creation:

test("Order everything") {
  val mockWarehouse = mockObject(Warehouse)
  val mockOrder = mock[Order]

  mockWarehouse.expects.inStock("Laphroig") returning 10
  mockOrder.expects.newInstance("Laphroig", 10)

  Order.largestPossible("Laphroig")
}

This uses two mock objects, one representing the Warehouse object and one representing an (as yet uncreated) instance of the Order class:

  val mockWarehouse = mockObject(Warehouse)
  val mockOrder = mock[Order]

The interesting line is this one:

  mockOrder.expects.newInstance("Laphroig", 10)

Which sets an expectation that a new instance of the Order class will be created with constructor arguments ("Laphroig", 10).

Building

Most of the cleverness happens within a Scala compiler plugin. Compiling tests that use mocks requires three steps:

  1. Compile the code you want to test as normal.
  2. Compile this code again with the Borachio compiler plugin enabled. The plugin will generate mock source code for any classes and objects mentioned in @mock @mockObject annotations.
  3. Compile this code together with your test code

These steps have all been collected into an sbt plugin, so all you need to do is mix the GenerateMocks trait into your project definition and everything else should happen automagically.

Under the hood

Given the Order class in our example above, the compiler plugin generates two different classes, one with the same name (i.e. Order) and one called Mock$Order. Here’s (a simplified version of) what they look like:

class Order(dummy: MockConstructorDummy) extends Mock$Order {

  def fill(): Unit = mock$0()
  def this(product: String, quantity: Int) = {
    this(new MockConstructorDummy)
    mock$1(product, quantity)
  }

  lazy val mock$0 = new MockFunction0[Unit]
  lazy val mock$1 = new MockConstructor2[String, Int, Order]
}

trait Mock$Order {

  val expects = new {
    def fill() = mock$0.expects()
    def newInstance(product: String, quantity: Int) =
      mock$1.expects(product, quantity)

    lazy val mock$0 = // reference to mock$0 in Order
    lazy val mock$1 = // reference to mock$1 in Order
  }
}

Borachio then uses a custom class loader to ensure that, when the code being tested tries to load the Order class, it gets the generated version above (calls get forwarded to the “real” Order class if mocking isn’t in force).

To do

I hope that this code is complete enough to be useful. Certainly it’s complete enough that I’d welcome feedback on anything that doesn’t work or could be done better. The things that still need doing are:

  • Android and JUnit3 integration
  • Support for mocking private constructors and methods
  • Forwarding to non-mock singleton functionality (this is in place for classes, not yet for singleton objects)
  • Static method (Java) support
  • Support for type-parameterised methods