Towards typesafe native Scala mocks in Borachio

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

Borachio, my native mocking library for Scala, works well enough but has a couple of annoying limitations:

  • It can only mock functions and traits (interfaces). No support for classes, companion objects, constructors, etc. etc. etc.
  • It’s not typesafe.

I’ve been working to address those limitations and, although I’m far from done, I’ve made enough progress to be worth reporting.

Warning: The code described here is experimental and very likely to change. Use at your own risk, and don’t be surprised if things break in the future.

This experimental version of Borachio (the code is in the typesafemocks branch and a compiled snapshot is available here) uses a Scala compiler plugin to generate typesafe mock objects. These mock objects can extend traits or classes.

Unfortunately, current limitations of the Scala compiler plugin architecture mean that it’s effectively impossible to generate methods within a compiler plugin (see this discussion to understand why). So the Borachio plugin instead requires two passes – one pass to generate the source and then a second pass to compile it.

There’s an example of its use (an updated version of the Turtle example) available on GitHub.

The key class in this example is Turtle:

class Turtle {
  def penUp() { ... }
  def penDown() { ... }
  def forward(distance: Double) { ... }
  def turn(angle: Double) { ... }
  def getAngle = { ... }
  def getPosition = { ... }
}

In the first pass, the compiler is called with the -P:borachio:generatemocks argument. This causes the plugin to look for @mock annotations to determine which classes it needs to generate mocks for. Here’s how we specify that we want to create mock turtles:

@mock(classOf[Turtle])
class Dummy

And here’s what the plugin generates as a result:

class MockTurtle(factory: com.borachio.AbstractMockFactory) extends com.example.Turtle {
  override def getPosition = mock$getPosition()
  override def getAngle = mock$getAngle()
  override def turn(angle: Double) = mock$turn(angle)
  override def forward(distance: Double) = mock$forward(distance)
  override def penDown() = mock$penDown()
  override def penUp() = mock$penUp()

  val expects = new {
    def getPosition: com.borachio.TypeSafeExpectation[(Double, Double)] = mock$getPosition.expects()
    def getAngle: com.borachio.TypeSafeExpectation[Double] = mock$getAngle.expects()
    def turn(angle: com.borachio.MockParameter[Double]): com.borachio.TypeSafeExpectation[Unit] = mock$turn.expects(angle)
    def forward(distance: com.borachio.MockParameter[Double]): com.borachio.TypeSafeExpectation[Unit] = mock$forward.expects(distance)
    def penDown(): com.borachio.TypeSafeExpectation[Unit] = mock$penDown.expects()
    def penUp(): com.borachio.TypeSafeExpectation[Unit] = mock$penUp.expects()
  }

  private val mock$getPosition = new com.borachio.MockFunction0[(Double, Double)](factory, 'getPosition)
  private val mock$getAngle = new com.borachio.MockFunction0[Double](factory, 'getAngle)
  private val mock$turn = new com.borachio.MockFunction1[Double, Unit](factory, 'turn)
  private val mock$forward = new com.borachio.MockFunction1[Double, Unit](factory, 'forward)
  private val mock$penDown = new com.borachio.MockFunction0[Unit](factory, 'penDown)
  private val mock$penUp = new com.borachio.MockFunction0[Unit](factory, 'penUp)
}

This can then be used in tests like this:

class ControllerTest extends Suite with MockFactory {

  val mockTurtle = new MockTurtle(this)
  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(~(Pi / 4))
      mockTurtle.expects.forward(~sqrt(2.0))
      mockTurtle.expects.getAngle.returning(Pi / 4)
      mockTurtle.expects.turn(~(-Pi / 4))
      mockTurtle.expects.penDown
      mockTurtle.expects.forward(1.0)
    }

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

There’s still a huge amount left to do:

  • Support for type-parameterised methods
  • Support for classes with non-trivial constructors
  • Support for final classes or classes with final methods or private constructors
  • Support for companion objects
  • etc. etc. etc.

And hopefully the limitations on code generation in Scala compiler plugins will be lifted soon, meaning that the two-stage compilation process won’t be necessary either.

0 Responses to “Towards typesafe native Scala mocks in Borachio”



  1. Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s





Follow

Get every new post delivered to your Inbox.

Join 219 other followers

%d bloggers like this: