25 February 2008

Better mocks with jMock (and specs)

Now, I don't know how I managed to do without it. Now, I wonder how everyone can do without it. Now, I think that nothing big can be done without it.

I'm not advertising a brand new toy, but I'm talking about mock objects. Whether they are real mocks or just stubs, it is almost impossible to unit test Java components without them. Not that you can't test them but if you really want to isolate a piece of code, mocks show up one way or the other.

One of the best libraries for mock objects in Java is jMock. Yet, Java's verbosity makes it sometimes difficult to understand the intention of the mock expectations. Enter now jMocks with Scala!

Some statistics about my blog

Let's say I want to write another front-end to publish my posts to Blogger. I have encapsulated all Blogger functionalities in a Scala trait:

trait Blogger {
def allPosts: List[Post]
def todayPosts: List[Post]
def post(p: Post, tags: List[Tag]): Unit

Now I want to test a "Statistics" component which will compute some stats about my posts:

object statsSpecification extends Specification with JMocker {
"a statistics component" should {
"return the number of posts for today" in {
val blogger = mock(classOf[Blogger])
val stats = new Statistics(blogger)
expect {
one(blogger).todayPosts will returnValue(List(Post("...")))
class Statistics(blogger: Blogger) {
def numberOfPostsForToday: Int = blogger.todayPosts.size

In that short specification we:
  1. create a mock: blogger = mock(classOf[Blogger]). I would have preferred to write blogger = mock[Blogger] but there is no way in Scala to create an object from its type only

  2. Add an expectation in the expect block. Again here the loan pattern makes things a lot clearer than the corresponding "Double-brace block" in Java (even if it is a clever java trick!).

  3. Specify what the return value should be in the same expression by defining "will" as Scala infix operator. In the Java equivalent we would have to make a separate method call (which our favorite IDE may insist on putting on the next line!)
    one(blogger).todayPosts; will(returnValue(List(Post("..."))))
Pushing further with nested expectations

There is also a situation where using Scala and jMock could be a real win.
[What follows is extracted from the specs Wiki, talk about reusability!]

You need to mock an object, like a Connection, which is supposed to give you access to a service, that you also want to mock and so on. For example, testing some code accessing the Eclipse platform can be very difficult for that reason.

Using specs you can use blocks to specify nested expectations:

// A workspace gives access to a project and a project to a module
case class Module(name: String)
case class Project(module: Module, name: String)
case class Workspace(project: Project)
val workspace = mock(classOf[Workspace])

expect {
one(workspace).project.willReturn(classOf[Project]) {p: Project =>
// nested expectations on project
one(p).name willReturn "hi"
one(p).module.willReturn(classOf[Module]) {m: Module =>
// nested expectation on module
one(m).name willReturn "module"}


// a workspace is a list of projects
case class Project(name: String)
case class Workspace(projects: List[Project])
val workspace = mock(classOf[Workspace])
expect {
// the workspace will return project mocks with different expectations
one(workspace).projects willReturnIterable(classOf[Project],
{p: Project => one(p).name willReturn "p1" },
{p: Project => one(p).name willReturn "p2" })

I haven't yet tested this capability on a real project but I clearly remember having had that kind of requirement.

I hope that this short post can make you feel that using mocks can be easy and elegant, especially if you use them with Scala! (and specs,....)

PS: Thanks again to Lalit Pant for showing the way with Scala and jMock

No comments: