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.
- Create a directory for our new project:
$ mkdir mockturtle $ cd mockturtle
- Create a build definition file called
build.sbtcontaining: - Create
src/main/scala/Turtle.scalacontaining: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) }
- 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.scalacontaining: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)) } }
- 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.scalacontaining: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)) } }
- Run the tests with
sbt test. You should see “[success]“
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" )
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.
