Testing with jMock
jMock is a lightweight testing framework that lets you do test driven development in Java. It lets you mock the actual objects, that is, create fake ones, and use them in your test cases instead of the real ones. This is an extremely useful and handy approach since you can actually work on your project without having the actual class implementations in hand; jMock lets you define the expected behaviour. The only thing you need to have is the interfaces of the classes you are mocking. JMock works only with interfaces (no abstract or concrete classes) so you need to have the interfaces ready before you go on testing.
Lets think of a very simple example. Lets say that we have a class called Computer
that computes a few things. It does a multiplication, appends the result to the end of a string, reverses the string and returns it to the caller. Ok, it’s a trivial and naive example but it will demonstrate the purpose of jmock. Before we start you need to download the jMock 2.4 release and junit. You can download any version of JUnit; I have the 3.8 installed on my computer so I will be using this one throughtout this post. But replacing the 3.8 JUnit code with the latest annotated version of JUnit should not be hard to do. After downloading the zip files you will need to put the junit.jar
, jmock-2.4.0.jar,
and hamcrest-library-1.1.jar
in your classpath. The jMock site states that you should also include the hamcrest-core-1.1.jar
but the tests I wrote work fine without it.
Lets start by defining first our test class. We call it ComputerTest
since it will be testing the Computer
class. jMock will be used with the ComputerTest
class. We will need to create all the mock objects in there. It should be a very simple class that extends the JUnit TestCase
for the beginning
public class ComputerTest extends TestCase { }Now it’s time to think about the functionality of the test method. The use case said that the
Computer
clas will need to do a multiplication, append the result to a string and then reverse this string. Lets assume that we need three different classes to do that. We need aCalculator
class, anAppender
class and aReverser
class. These should be interfaces (to be able to use them with jMock) and each one of them should define at least one method.interface Calculator { /** * Multiplies two integer values and returns the result. * @param a the first value. * @param b the second value. * @return the product of the multiplication. */ Integer multiply(final int a, final int b); } interface Appender { /** * Appends an integer value to the end of the string. * @param value the integer value to be appended. * @return the string with the appended value. */ String append(final Integer value); } public interface Reverser { /** * Reverses a string. * @param toBeReversed the string to be reversed. * @return the reversed string. */ String reverse(String toBeReversed); }Now in out test class we will need to mock these interfaces. We can do that by using the
Mockery
object. Once the objects are mocked they can be passed on to theComputer
class that will use them. Note that we can only test for objects that have been mocked by usingMockery
since jMock only knows about these objects. If we try to test a non-mocked object jMock will complain. Lets define in our test class and mock the objects we are interested inpublic class ComputerTest extends TestCase { private Mockery mockery; private Calculator calculator; private Appender appender; private Reverser reverser; private Computer computer; /** * {@inheritDoc} */ @Override public void setUp() { mockery = new Mockery(); calculator = mockery.mock(Calculator.class); appender = mockery.mock(Appender.class); reverser = mockery.mock(Reverser.class); computer = new Computer(calculator, appender, reverser); } }We can see that by using
mockery.mock
we can create mock objects that we pass into ourComputer
class. Since we pass these objects using the constructor theComputer
class should look something likepublic class Computer { private Calculator calculator; private Appender appender; private Reverser reverser; /** * Constructor. * * @param calculator the calculator class. * @param appender the appender class. * @param reverser the reverser class. */ public Computer(final Calculator calculator, final Appender appender, final Reverser reverser) { this.calculator = calculator; this.appender = appender; this.reverser = reverser; } }Now it’s time to think what we need to test. The end result is a reversed string, so
Computer
should have a method that returns a string. This is the method that should be tested from the test class. We can call this methodcompute
. Thecompute
method should multiply, append and then reverse the string. To do that it will need to call the methods on the objects passed into theComputer
. We can test these steps one by one in our test method. For the first step (multiply) we can have something like the followingpublic class ComputerTest extends TestCase { private Mockery mockery; private Calculator calculator; private Appender appender; private Reverser reverser; private Computer computer; /** * {@inheritDoc} */ @Override public void setUp() { mockery = new Mockery(); calculator = mockery.mock(Calculator.class); appender = mockery.mock(Appender.class); reverser = mockery.mock(Reverser.class); computer = new Computer(calculator, appender, reverser); } /** * Tests the compute method. */ public void testCompute() { final Integer product = Integer.valueOf(200); final int a = 10, b = 20; mockery.checking(new Expectations(){ { one(calculator).multiply(a, b); will(returnValue(product)); } }); computer.compute(a, b); mockery.assertIsSatisfied(); } }The
mockey.checking()
method checks for the expectations we set. We set these expectations as anExpectations
object, in a initialisation block of code (there are other ways to set expectations, for these you can have a look at JMock’s web site). Remember again that we do not test the functionality of the class but we test the behaviour of it. We don’t test if the class returns a valid result, we only test that the class performs the steps necessary to return a result. So in our example above when the statementcomputer.compute(a, b)
is called jMock tests and asserts (with themockery.assertisSatisifed()
statement) that our expectations are satisfied. In the above case it will test if themultiply
method of thecalculator
object is called one, and only one, time in theComputer
‘scompute
method with the parameters 10 and 20. If it is then the test case will succeed, otherwise it will fail. If thecompute
method is something like the followingpublic String compute(final int a, final int b) { return ""; }then the test method will fail with the error message
not all expectations were satisfied expectations: expected exactly 1 time, never invoked: calculator.multiply(<10>, <20>); returns <200>which means exactly what the error says. jMock expected a call of the
calculator.multiply
method one time but it was never called. This means that thecompute
method does not call themultiply
method on theCalculator
object. To resolve this issue and make the test case pass we should add a statement in thecompute
method that does the callpublic String compute(final int a, final int b) { Integer product = calculator.multiply(a, b); return ""; }By adding a
calculator.multiply
statement the test should pass. Also note that we can control the output of the call by using thewill(returnValue(product))
statement. This lets us specify the values we want to return from a method call. We say to jMock that the call tocalculator.multiply
will return a value that is equal to theproduct
variable. If you do not need to use this value later in your application you might as well ignore thewill(return)
call.Lets add the last two calls to our test method and finish it.
public void testCompute() { final Integer product = Integer.valueOf(200); final int a = 10, b = 20; final String someString = "a string "; mockery.checking(new Expectations(){ { one(calculator).multiply(a, b); will(returnValue(product)); one(appender).append(product); String appendedString = someString + product.toString(); will(returnValue(appendedString)); one(reverser).reverse(appendedString); } }); computer.compute(a, b); mockery.assertIsSatisfied(); }Here we simulate the last two calls to the objects we are interested in. One call to the
append
method of theappender
object and a second call to thereverse
method of thereverser
object. Of course this will fail since thecompute
method does not have these calls. We will need to change it to the following/** * Calculates the product of two numbers, appends it to a String and then reverses this string. * * @param a the first number. * @param b the second number. * @return a reversed string. */ public String compute(final int a, final int b) { Integer product = calculator.multiply(a, b); String s = appender.append(product); return reverser.reverse(s); }This will make the test code pass since all the behaviour is satisfied (all the objects call the methods specified).
I really hope you will find jMock useful since it’s extremelly powerful. If you are new to testing you might find it a bit steep to learn jMock but the secret is to keep at it. Keep experimenting and try different things. If all else fails leave a comment here and if I know I will try ot help you. The full code of the example is shown belowpackage jmock24; import junit.framework.TestCase; import org.jmock.Expectations; import org.jmock.Mockery; /** * * @author panos */ public class ComputerTest extends TestCase { private Mockery mockery; private Calculator calculator; private Appender appender; private Reverser reverser; private Computer computer; /** * {@inheritDoc} */ @Override public void setUp() { mockery = new Mockery(); calculator = mockery.mock(Calculator.class); appender = mockery.mock(Appender.class); reverser = mockery.mock(Reverser.class); computer = new Computer(calculator, appender, reverser); } /** * Tests the compute method. */ public void testCompute() { final Integer product = Integer.valueOf(200); final int a = 10, b = 20; final String someString = "a string "; mockery.checking(new Expectations(){ { one(calculator).multiply(a, b); will(returnValue(product)); one(appender).append(product); String appendedString = someString + product.toString(); will(returnValue(appendedString)); one(reverser).reverse(appendedString); } }); computer.compute(a, b); mockery.assertIsSatisfied(); } } package jmock24; /** * * @author panos */ interface Calculator { /** * Multiplies two integer values and returns the result. * @param a the first value. * @param b the second value. * @return the product of the multiplication. */ Integer multiply(final int a, final int b); } package jmock24; /** * * @author panos */ interface Appender { /** * Appends an integer value to the end of the string. * @param value the integer value to be appended. * @return the string with the appended value. */ String append(final Integer value); } package jmock24; /** * * @author panos */public interface Reverser { /** * Reverses a string. * @param toBeReversed the string to be reversed. * @return the reversed string. */ String reverse(String toBeReversed); } package jmock24; /** * * @author panos */ public class Computer { private Calculator calculator; private Appender appender; private Reverser reverser; /** * Constructor. * * @param calculator the calculator class. * @param appender the appender class. * @param reverser the reverser class. */ public Computer(final Calculator calculator, final Appender appender, final Reverser reverser) { this.calculator = calculator; this.appender = appender; this.reverser = reverser; } /** * Calculates the product of two numbers, appends it to a String and then reverses this string. * * @param a the first number. * @param b the second number. * @return a reversed string. */ public String compute(final int a, final int b) { Integer product = calculator.multiply(a, b); String s = appender.append(product); return reverser.reverse(s); } }
Testing a stored procedure using hsqldb
Today I needed to test a data access object that will be calling a stored procedure in the database. Since we are using HSQLDB and mock objects to do all the database interactions while we develop, I needed to test it without having access to the actual stored procedure. At first I wasn’t sure how to do it. Then I started looking at the HSQLDB documentation and I realised that I could create an alias to a static method and treat it exactly like I could treat the stored procedure.
Lets say that the stored procedure returns a random number from 1 to 100. In my test class I define a static method that returns this number
public static int random()
{
return 1 + (int)(Math.random() * 100);
}
Then I create an alias to this method call
// Define the alias to the stored procedure as a
// static final variable
private static final String SP =
"CREATE ALIAS randomStoredProcedure
FOR\"my.class.DaoTester.random\"";
Note that I needed to define the full class name of my test class, including the package. I also used double quotes around the class declaration since HSQLDB automatically converts all lower case letters to upper case.
Once the stored procedure is loaded up in the in-memory database using
// Load the alias.
jdbc.update(SP);
it can be called from the actual DAO implementation class like you would call any other stored procedure
// Call it to get the random number.
int random = jdbc.queryForInt("{call randomStoredProcedure()}");
The jdbc
variable is of type SimpleJdbcOperations
from the Spring framework. The implementation class can be used without changes with the real stored procedure. Neat.