Archive for February, 2011

Mocking in Scala with Borachio step-by-step

Borachio is now ScalaMock.

Recently, I announced Borachio, native mocking for Scala. This post is a full worked example of using Borachio with ScalaTest and sbt.

The example assumes that we’re writing code to control a mechanical turtle, similar to that used by Logo programs. Mocking is useful in this kind of situation because we might want to create tests that function even if we don’t have the hardware to hand, which run more quickly than would be the case if we ran on real hardware, and where we can use mocks to simulate errors or other situations difficult to reproduce on demand.

The code for this example is available on GitHub.

  1. Create a directory for our new project:
    $ mkdir mockturtle
    $ cd mockturtle
  2. Create a build definition file called build.sbt containing:
  3. name := "Mock Turtle"
    
    version := "2.0"
    
    scalaVersion := "2.9.1"
    
    libraryDependencies ++= Seq(
      "org.scalatest" %% "scalatest" % "1.6.1" % "test",
      "com.borachio" %% "borachio-scalatest-support" % "latest.integration"
    )
  4. Create src/main/scala/Turtle.scala containing:
    package com.example
    
    trait Turtle {
      def penUp()
      def penDown()
      def forward(distance: Double): (Double, Double)
      def turn(angle: Double)
      def getAngle: Double
      def getPosition: (Double, Double)
    }
  5. The turtle API is not very convenient, we have no way to move to a specific position, instead we need to work out how to get from where we are now to where we want to get by calculating angles and distances. Here’s some code that draws a line from a specific point to another by doing exactly that.

    Create src/main/scala/Controller.scala containing:

    package com.example
    
    import scala.math.{atan2, sqrt}
    
    class Controller(turtle: Turtle) {
    
      def drawLine(start: (Double, Double), end: (Double, Double)) {
        moveTo(start)
    
        val initialAngle = turtle.getAngle
        val deltaPos = delta(start, end)
    
        turtle.turn(angle(deltaPos) - initialAngle)
        turtle.penDown
        turtle.forward(distance(deltaPos))
      }
    
      def delta(pos1: (Double, Double), pos2: (Double, Double)) =
        (pos2._1 - pos1._1, pos2._2 - pos1._2)
    
      def distance(delta: (Double, Double)) =
        sqrt(delta._1 * delta._1 + delta._2 * delta._2)
    
      def angle(delta: (Double, Double)) =
        atan2(delta._2, delta._1)
    
      def moveTo(pos: (Double, Double)) {
        val initialPos = turtle.getPosition
        val initialAngle = turtle.getAngle
    
        val deltaPos = delta(initialPos, pos)
    
        turtle.penUp
        turtle.turn(angle(deltaPos) - initialAngle)
        turtle.forward(distance(deltaPos))
      }
    }
  6. So let’s test that this is doing the right thing. We’ll create a mock Turtle that pretends to start at the (0, 0) and verifies that if we ask the code we’ve just written to draw a line from (1, 1) to (2, 1), it performs the correct sequence of turns and movements.

    Create src/test/scala/ControllerTest.scala containing:

    package com.example
    
    import org.scalatest.Suite
    import com.borachio.scalatest.MockFactory
    import scala.math.{Pi, sqrt}
    
    class MockFunctionTest extends Suite with MockFactory {
    
      val mockTurtle = mock[Turtle]
      val controller = new Controller(mockTurtle)
    
      def testDrawLine() {
        inSequence {
          mockTurtle expects 'getPosition returning (0.0, 0.0)
          mockTurtle expects 'getAngle returning 0.0
          mockTurtle expects 'penUp
          mockTurtle expects 'turn withArgs (~(Pi / 4))
          mockTurtle expects 'forward withArgs (~sqrt(2.0))
          mockTurtle expects 'getAngle returning Pi / 4
          mockTurtle expects 'turn withArgs (~(-Pi / 4))
          mockTurtle expects 'penDown
          mockTurtle expects 'forward withArgs (1.0)
        }
    
        controller.drawLine((1.0, 1.0), (2.0, 1.0))
      }
    }
  7. Run the tests with sbt test. You should see “[success]”

So how does this work? First, we create a mock object that implements the Turtle trait, and pass that to an instance of Controller that we’ll test later:

  val mockTurtle = mock[Turtle]
  val controller = new Controller(mockTurtle)

Then, in our test, we start by setting up what we expect to happen. In this case, ordering is important, so we ensure that our functions are called in order using inSequence:

    inSequence {
      // expectations
    }

We list which methods we expect to be called, together with their arguments. In addition, where it’s important for the functionality we’re testing, we also specify the values that our mock object should return. There’s a wrinkle, however, because we’re dealing with floating-point numbers. If we test for simple equality, rounding errors are likely to stop our tests from passing. That’s where the ~ (tilde) operator comes in:

      mockTurtle expects 'forward withArgs (~sqrt(2.0))

This says that we expect the forward method to be called with a single argument which is “close to” √2. Borachio also supports wildcard parameters (not used here) specified with an * (asterisk).

Finally, we call our code under test with the appropriate arguments:

    controller.drawLine((1.0, 1.0), (2.0, 1.0))

Updated 2011-09-21

Updated for sbt 0.10.x and Borachio 1.3.

Announcing Borachio: Native Scala mocking

Borachio is now ScalaMock.

I’ve recently been working in my spare time on a native Scala mocking framework.

I now have something which, although far from “done”, is close enough that I think it should be useful. So I’d like to announce and solicit feedback on Borachio – a native Scala mocking framework:

Homepage: http://borachio.com/
GitHub: https://github.com/paulbutcher/borachio
Documentation: http://borachio.com/api/com/borachio/package.html

A couple of examples:

def testTurtle {
  val t = mock[Turtle]

  t expects 'penDown
  t expects 'turn withArgs (90.0)
  t expects 'forward withArgs (10.0)
  t expects 'getPosition returning (0.0, 10.0)

  drawLine(t)
}
def testFoldLeft() {
  val f = mockFunction[String, Int, String]

  f expects ("initial", 0) returning "intermediate one"
  f expects ("intermediate one", 1) returning "intermediate two"
  f expects ("intermediate two", 2) returning "intermediate three"
  f expects ("intermediate three", 3) returning "final"

  expect("final") { Seq(0, 1, 2, 3).foldLeft("initial")(f) }
}