When I ported Jiva to Scala, I did not really know if I could continue to use JMock for my testing needs. As it turned out, JMock worked out very well for testing Scala code.
I could go in and try to explain some of the ways in which JMock is used within Jiva, but I'm afraid that will introduce a lot of Genetic Algorithms related terminology, and will potentially divert attention from the main point of this post. So I will instead demonstrate the usage of JMock with Scala via a very simple (and contrived) example. If you want to see JMock in action within Jiva, you are welcome to browse the Subversion repository for the project:
http://jiva-ng.googlecode.com/svn/trunk/src/test/scala/net/kogics/jiva/operators/
Back to the simple example...
Let's say we have a Class-under-test (Cut) in our code, and a Helper class that is used by Cut:
The Helper class has three different helper methods with different signatures. Cut also has three methods that delegate to the corresponding Helper methods. The idea here is that we want to test the Cut class by mocking out the helper used by Cut.
class Helper {
def help = "helpfulResult"
def helpWith(problem: String) = problem + " whatever help for String problem"
def helpWith(problem: int) = problem + " whatever help for int problem"
}
class Cut(helper: Helper) {
def doWork: String = {
helper.help + " processed"
}
def doWork(problem: String): String = {
helper.helpWith(problem) + " str processed"
}
def doWork(problem: int): String = {
helper.helpWith(problem) + " int processed"
}
}
Let's jump right into the Test class (you will need to enable Java generics support in Scala for this to work; more on that later):
This should be pretty straightforward for those of you who are familiar with JMock, except for a couple of things:
import junit.framework.TestCase
import junit.framework.Assert._
import org.jmock.Mockery
import org.jmock.lib.legacy.ClassImposteriser
import net.kogics.jiva.util.mock.SExpectations
import org.jmock.Expectations._
class TestCut extends TestCase {
val context = new Mockery() {
{
// we want to be able to mock classes
setImposteriser(ClassImposteriser.INSTANCE);
}
};
def test1 = {
// create the mock
val helper = (context.mock(classOf[Helper])).asInstanceOf[Helper]
// setup mock return values
val noOpHelpReturn = "mockHelp"
val strHelpReturn = "mockHelp stringResult"
val intHelpReturn = "mockHelp intResult"
// set Expectations
context.checking(
new SExpectations {
one(helper).help
will(returnValue(noOpHelpReturn));
exactly(1).of(helper).helpWith(withArg(any(classOf[String])))
will(returnValue(strHelpReturn));
atLeast(1).of(helper).helpWith(withArg(any(classOf[int])))
will(returnValue(intHelpReturn));
})
// setup the class under test with the mock
val cut = new Cut(helper)
// exercise the class under test
// and verify results
val result = cut.doWork
assertEquals(result, noOpHelpReturn + " processed")
val result2 = cut.doWork("task")
assertEquals(result2, strHelpReturn + " str processed")
val result3 = cut.doWork(3)
assertEquals(result3, intHelpReturn + " int processed")
// make sure that the expectations on the mock were satisfied
context.assertIsSatisfied
}
}
- We set up Expectations using something called SExpectations.
- We specify method paramater expectations using a method called withArg, as opposed to the more familiar with method provided by JMock
SExpectations provides a method called withArg that just calls into the standard with method provided within its superclass by JMock. So essentially this class functions as an adapter that allows JMock parameter matching to be used by code written in Scala.
package net.kogics.jiva.util.mock;
import org.hamcrest.Matcher;
import org.jmock.Expectations;
public class SExpectations extends Expectations {
public <T> T withArg(Matcher<T> matcher) {
return super.with(matcher);
}
}
Update: as pointed out by James Iry in his comment, we can solve this problem right within Scala-land, with the help of identifier quoting using backtics. Here's SExpectations in Scala:
This version of SExpectations can be used in the scala test code in exactly the same way as the Java version. Alternatively, the with method in Expectations can be called directly in the test code with the help of backticks. Both these approaches are shown in the sample code below:
class SExpectations extends Expectations {
def withArg[T](matcher: Matcher[T]): T = super.`with`(matcher)
}
That's about it, actually. To reiterate: if you know how to use JMock (with Java), it should be pretty straightforward for you to start using it with Scala.
// set Expectations
context.checking(
new SExpectations {
one(helper).help
will(returnValue(noOpHelpReturn));
exactly(1).of(helper).helpWith(withArg(any(classOf[String])))
will(returnValue(strHelpReturn));
atLeast(1).of(helper).helpWith(`with`(any(classOf[int])))
will(returnValue(intHelpReturn));
})
Some additional comments:
- To work with JMock, you need to use Scala with the -Ygenerics and -target:jvm-1.5 options.
- I used JUnit 3 for my Scala unit tests because the Scala Eclipse plugin does not quite support annotations. The standalone/command-line version of Scala supports annotations just fine, so it should be very possible to use JMock/JUnit 4 with Scala
- I suppose I could have used MockObjectTestCase as a base class for my Tests. I just wanted to have more control over things as I worked through the Scala/JMock integration
- I used the CGLib based Legacy ClassImposteriser because I needed to mock system classes for testing Jiva code
- Versions used - Scala: 2.6.0, JMock: 2.4.0
8 comments:
Nice post Lalit. It's a useful framework and it's good for people to see that Scala can interact with Java cleanly.
driven by the same reason: with is a reserved word in Scala
The Scala group realized that there was a potential conflict if they didn't share exactly the same keywords as Java, which would have been too limiting. So they put an 'out' in the language: backticks. If you replace
helpWith(withArg(any(classOf[String])))
with
helpWith(`with`(any(classOf[String])))
then it all should work. Equivalently, if typing backticks all the time is annoying, you could re-implement SExpectation in Scala so that withArg is implemented in terms of `with`. You might also be able to introduce an implicit conversion so SExpectation doesn't extend Expectation - it just 'adds' methods.
In other words, no need to write any Java :-)
Thanks for the very useful feedback, James. I have updated the post based on your comments.
PS. I could not quite figure out how an implicit conversion could be used in this case.
Hi Lalit,
I am also a jMock fan in Java. However in Scala I have created a BDD framework which works with other assertions than jMock ones. This is why I also created a lightweight framework which is more verbose than jMock. I would like to have your opinion on this if you have some time: using mocks with specs
Thanks,
Eric.
Specs looks very interesting, Eric. I will check it out...
Great! I had left a link in the comment but I didn't display properly. Here it is : http://code.google.com/p/specs/wiki/UsingMocks
Cheers,
Eric.
Hi Lalit,
You may know that already but there is now an integration of jMock with specs.
I wrote a short post about it and the doc is on the wiki.
Hi Eric,
Thanks for the update.
Specs is starting to look really powerful. Congratulations for all the good work...
JDave (a BDD framework) has also gotten support for Scala, making some of the JMock stuff nicer, in my opinion.
Post a Comment