Mock objects mimic the behavior of classes written with the sole purpose of guiding code execution so that it falls within those areas under test. In time, the number of mock objects can grow with the number of application classes. Frameworks such as jMock, RMock, and even EasyMock help eliminate the need for a physical and separately existing set of mock objects.
One major disadvantage of the EasyMock framework is that you can't mock concrete classes — only interfaces. In this article, I show how you can use the jMock framework to mock concrete classes and interfaces, as well as how to test certain obscure cases with RMock.
Configure jMock and RMock in the Eclipse IDE
Note: See Resources for the latest binaries for JUnit, jMock, and RMock.
Begin by launching the Eclipse integrated development environment (IDE). Next, create a
basic Java™ project into which you'll import the JUnit, jMock, and RMock Java Archive (JAR) libraries. Name the Java project TestingExample. Within the Java Perspective, choose Project > Properties, then click the Libraries tab,
as shown below.
Figure 1. Editing properties for TestingExample project in Eclipse
Use the Add JARs button when the JAR files are in the Java classpath (that is, the Java Runtime Environment (JRE) you have configured within Eclipse). The Add Variable button applies to a specific directory in which resources (including JARs) on a file system (local or remote) reside and can be generally referenced. Use the Add Library button when you must reference those specific resources that are a default in Eclipse or configured for a specific Eclipse workspace environment. Click Add Class Folder to add a resource from one of the existing project folders already configured as part of the project.
For this example, click Add External JARs and browse to the jMock and RMock JARs you downloaded. Add them to the project. Click OK when the properties window shown in Figure 2 appears.
Figure 2. jMock and RMock JARs added to TestingExample Project
For the TestExample Project, you'll be working with the source code from four classes:
- ServiceClass.java
- Collaborator.java
- ICollaborator.java
- ServiceClassTest.java
The class under test will be ServiceClass, which contains a single method: runService().
The service method takes an object named a Collaborator, which implements a simple interface,
ICollaborator. A single method is implemented in the concrete Collaborator class:
executeJob(). Collaborator is the class you must mock appropriately.
The fourth class is the test class: ServiceClassTest. (The nature of the implementations has been simplified as
much as possible.) Listing 1 shows the code for this fourth class.
Listing 1. Service class sample code
public class ServiceClass {
public ServiceClass(){
//no-args constructor
}
public boolean runService(ICollaborator collaborator){
if("success".equals(collaborator.executeJob())){
return true;
}
else
{
return false;
}
}
}
|
In the ServiceClass class, the if...else code block is a simple logical crossroad that helps show
why the test would fail or pass if one path — and not another
— is taken, according to test expectations. The source code for the Collaborator class is shown below.
Listing 2. Collaborator class sample code
public class Collaborator implements ICollaborator{
public Collaborator(){
//no-args constructor
}
public String executeJob(){
return "success";
}
}
|
The Collaborator class is also simple, with a no-arguments constructor and a simple
String returned from the method executeJob(). The code below shows the code for the ICollaborator class.
public interface ICollaborator {
public abstract String executeJob();
}
|
The interface ICollaborator has a single method that must be implemented in the
Collaborator class.
With the above code in place, let's move on to examining how you can run your test of the ServiceClass class
successfully in different scenarios.
Scenario 1: Use jMock to mock interfaces
Testing the service method in the ServiceClass class is simple. Suppose the test requirement is to assert that
the runService() method did not run — in other words, that the Boolean result returned is false. In
such a case, the ICollaborator object passed to the runService() method is mocked
to expect a call on its method, executeJob(), and return a string other than "success." In this way, you ensure that the
Boolean string false is returned to the test.
The code for the ServiceClassTest class, which is shown
below, contains the test logic.
Listing 3. ServiceClassTest class sample code for scenario 1
import org.jmock.Mock;
import org.jmock.cglib.MockObjectTestCase;
public class ServiceClassTest extends MockObjectTestCase {
private ServiceClass serviceClass;
private Mock mockCollaborator;
private ICollaborator collaborator;
public void setUp(){
serviceClass = new ServiceClass();
mockCollaborator = new Mock(ICollaborator.class);
}
public void testRunServiceAndReturnFalse(){
mockCollaborator.expects(once()).method\
("executeJob").will(returnValue("failure"));
collaborator = (ICollaborator)mockCollaborator.proxy();
boolean result = serviceClass.runService(collaborator);
assertFalse(result);
}
}
|
It is generally a good idea to include a setUp() method in
your tests if common operations are performed across different test cases. A tearDown() method is also a good idea, but not strictly necessary, unless you're running integration tests.
Also note that with jMock and RMock, the framework checks all expectations across all
mock objects at the end of, or during, the test run. There is no real need to include the verify() method for each mock's
expectations. When run as a JUnit test, the test passes, as shown below.
Figure 3. Scenario 1 test pass
The ServiceTestClass class extends the jMock CGLIB's
org.jmock.cglib.MockObjectTestCase class. The mockCollaborator is a simple
org.jmock.JMock class. Typically, there are two ways to produce mock objects with jMock:
- To mock an interface, use the
new Mock(Class.class)method - To mock a concrete class, use the
mock(Class.class, "identifier")method
It is important to note how a mock proxy is handed to the runService() method in the ServiceClass class. With jMock, you can extract proxy implementations
from created mock objects, on which expectations have already been set. This point will be essential in the later scenarios in this article, especially
where RMock is concerned.
Scenario 2: Use jMock to mock a concrete class with a default constructor
Suppose the runService() method in the ServiceClass class accepted only concrete implementations of the
Collaborator class. Would jMock be enough to ensure that the previous test passes without changing the
expectations? Yes, as long as you can construct the Collaborator class in a simple, default manner.
Change the runService() method in the ServiceClass class to reflect the code below.
Listing 4. Edited ServiceClass class for scenario 2
public class ServiceClass {
public ServiceClass(){
//no-args constructor
}
public boolean runService(Collaborator collaborator){
if("success".equals(collaborator.executeJob())){
return true;
}
else{
return false;
}
}
}
|
The ServiceClass class' if...else logic branch remains the same (for clarity). Also, the
no-arguments constructor is still in place. Note that it's not always necessary to have creative logic, such as while...do
clauses or for loops to test a class' methods appropriately. As long as there are method executions against objects the
class uses, simple mock expectations are sufficient to test those executions.
You must also change the ServiceClassTest class to suit the scenario, as shown below.
Listing 5. Edited ServiceClassTest class for scenario 2
...
private ServiceClass serviceClass;
private Mock mockCollaborator;
private Collaborator collaborator;
public void setUp(){
serviceClass = new ServiceClass();
mockCollaborator = mock(Collaborator.class, "mockCollaborator");
}
public void testRunServiceAndReturnFalse(){
mockCollaborator.expects(once()).method("executeJob").will(returnValue("failure"));
collaborator = (Collaborator)mockCollaborator.proxy();
boolean result = serviceClass.runService(collaborator);
assertFalse(result);
}
}
|
There are a few points to note here. First, the runService()
method signature has changed from earlier. Instead of accepting an ICollaborator interface, it now accepts a concrete class
implementation (the Collaborator class). This change is
significant as far as the testing framework is concerned. (Note that though anti-polymorphic in nature, we're using the example of passing a
concrete class for the purposes of the example only. It should never be done in a true object-oriented fashion).
Second, the manner of mocking the Collaborator class has changed. The jMock CGLIB library makes it possible
to mock the concrete class implementation. The extra String parameter supplied to jMock CGLIB's
mock() method is used as an identifier for the mock
object created. When using jMock (and, indeed, RMock),
unique identifiers are required per mock object setup within a single test case. This is true for mock objects defined in a common
setUp() method or within the actual test method.
Third, the original expectation of the test method has not changed. A false assertion is still required for the test to pass. This is important because by showing how the testing frameworks used are flexible enough to accommodate change under different input while still allowing for constant test results, their true limitations are shown when the inputs cannot be accommodated to produce the same results.
Now, rerun the test as a JUnit test. The test passes, as shown below.
Figure 4. Scenario 2 test pass
In the next scenario, things get slightly more complicated. You use the RMock framework to alleviate what may seem like a difficult situation with relative ease.
Scenario 3: Use jMock and RMock to mock a concrete class with a nondefault constructor
Start by attempting to use jMock, as before, to mock the Collaborator object — only this time, the
Collaborator doesn't have a default no-arguments constructor. Note that the test expectation of a Boolean false result is
maintained.
Also suppose that the Collaborator object requires a string
and a primitive int as parameters passed to the constructor. Listing 6 shows the changes made to the
Collaborator object.
Listing 6. Edited Collaborator class for scenario 3
public class Collaborator{
private String collaboratorString;
private int collaboratorInt;
public Collaborator(String string, int number){
collaboratorString = string;
collaboratorInt = number;
}
public String executeJob(){
return "success";
}
}
|
The Collaborator class constructor is still quite simple. The class fields are set with incoming parameters. No
other logic is necessary here, and its executeJob() function remains the same.
Rerun the test, with all other components of the example remaining the same. The result is a catastrophic test failure, as shown below.
Figure 5. Scenario 3 test failure
The test above was run as a simple JUnit test without code coverage. You can run any of the tests listed in this article with most code coverage tools (for example, Cobertura or EclEmma). There are some issues, however, when it comes to running RMock tests with code coverage inside Eclipse (see Table 1). The code below shows a snippet of the actual stack trace.
Listing 7. Stack trace for test failure in scenario 3
...Superclass has no null constructors but no arguments were given
at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:718)
at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)
at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:660)
.....
.....
|
The reason for the failure is that jMock cannot create a viable mock object from a class definition in which there is no no-arguments constructor.
The only way of instantiating the Collaborator object is to provide two simple arguments. You're now forced to find a way
of providing arguments to the mock object instantiation process to achieve the same effect, which is precisely why you use RMock.
Correct the broken test with RMock testing framework
To correct the test, a few modifications are in order. They may appear substantial, but in essence, they're a relatively simple workaround to leveraging the power of both frameworks to achieve your purpose.
The first change required is to make the test class an RMock TestCase, rather than a jMock CGLIB TestCase. The goal is to enable an easier configuration
of those mock objects belonging to RMock within the tests themselves and
— more importantly — during their initial setup. Experience shows that it's easier to construct and use mock
objects from both frameworks if the overall TestCase object from which the test class extends belongs to RMock.
Moreover, at first glance, it's a bit easier to quickly determine the flow of mock objects (flow here being used to describe a situation in which
you use a mock object as a parameter or even as a return type from other mock objects).
The second change required is to construct (at the very least) an object array holding the actual values of parameters to be passed into the
Collaborator class' constructor. It is also possible, for clarity's sake, to include a class-types array of the types the
constructor accepts and to pass that array, as well as the object array just described as parameters to instantiate the mock
Collaborator object.
The third change involves the construction of one or more expectations on the RMock mock object with the correct syntax. And the fourth and final change required is to bring the RMock mock object out of record state into ready state.
Listing 9 shows the final modifications to the ServiceClassTest class. It also shows the introduction of RMock and
its related functionality.
Listing 9. Fixing the ServiceClassTest class for scenario 3
...
import com.agical.rmock.extension.junit.RMockTestCase;
public class ServiceClassTest extends RMockTestCase {
private ServiceClass serviceClass;
private Collaborator collaborator;
public void setUp(){
serviceClass = new ServiceClass();
Object[] objectArray = new Object[]{"exampleString", 5};
collaborator =
(Collaborator)intercept(Collaborator.class, objectArray, "mockCollaborator");
}
public void testRunServiceAndReturnFalse(){
collaborator.executeJob();
modify().returnValue("failure");
startVerification();
boolean result = serviceClass.runService(collaborator);
assertFalse(result);
}
}
|
First, note that the test's expectations still haven't changed. The import of the RMockTestCase class heralds the
introduction of the RMock framework functionality. Next, the test class now
extends RMockTestCase, rather than MockObjectTestCase. Later, I'll show you the
reintroduction of MockObjectTestCase within a test case in which the TestClass object
is still of type RMockTestCase object.
Within the setUp() method, you instantiate an object array with the actual values the
Collaborator class' constructor needs. That array is summarily fed into RMock's
intercept() method to help instantiate the mock object. The signature of the method is similar to that of the jMock
CGLIB mock() method because both methods take on unique mock object identifiers as arguments. The class cast
of the mock object into type Collaborator is necessary because the intercept() method
returns type Object.
Within the test method itself, testRunServiceAndReturnFalse(), you can see a few more changes. The mock
Collaborator object's executeJob() method is called. At this stage, the mock object is in
record state
— that is, you're simply defining the method calls it will expect along the way. So, the mock is recording
expectations accordingly. The next line is a notification to the mock object to ensure that when it encounters the
executeJob() method, it should return the string failure. Therefore, with RMock, you state an expectation simply
by calling the method off the mock object (and passing any parameters it may need), then modifying that expectation to adjust any return types
accordingly.
Finally, the RMock method startVerification() is called to place the mock Collaborator
object into ready state. The mock object is now ready for use in the ServiceClass class as a real object. The
method is absolutely essential and must be called to avoid test initialization failures.
Rerun the ServiceClassTest once again to achieve the final positive result: The parameters you supplied during
mock object instantiation made all the difference. Figure 6 shows the positive green light of JUnit.
Figure 6. Scenario 3 test success with RMock
The assertFalse(result) code line represents the same test expectation from scenario 1, and RMock maintains test
success as jMock did earlier. In many ways, this is important, but the more dominant point here may be that the agile principle of fixing a
broken test has been exercised without changing the test expectation. The only difference is that an alternate framework was employed.
In the next scenario, you'll use both jMock and RMock in a special case. Neither framework on its own would achieve the correct result unless some alliance within the test is formed.
Scenario 4: Special collaboration between jMock and RMock
As mentioned, I wanted to examine a case in which the two frameworks must work together to achieve a certain result. Otherwise, a well-constructed test would fail every time. There are few cases in which it wouldn't matter whether you used jMock or RMock — where an interface or class that you want to mock exists in a JAR that is signed, for example. Such a situation is rare, but it may occur when testing code written against application program interfaces (APIs) in secure proprietary products (usually off-the-shelf software of one kind or another).
Listing 10 shows an example in which both frameworks play the test case out.
Listing 10. Test example for scenario 4
public class MyNewClassTest extends RMockTestCase{
private MyNewClass myClass;
private MockObjectTestCase testCase;
private Collaborator collaborator;
private Mock mockClassB;
public void setUp(){
myClass = new MyNewClass();
testCase = new MyMockObjectTestCase();
mockClassB = testCase.mock(ClassB.class, "mockClassB");
mockClassB.expects(testCase.once()).method("wierdMethod").
will(testCase.returnValue("passed"));
Class[] someClassArray = new Class[]{String.class, ClassA.class, ClassB.class};
Object[] someObjectArray = new Object[]
{"someArbitraryString", new ClassA(), (ClassB)mockClassB.proxy()};
collaborator = (Collaborator)intercept
(Collaborator.class, someClassArray, someObjectArray, "mockCollaborator");
}
public void testRMockAndJMockInCollaboration(){
startVerification();
assertTrue(myClass.executeJob(collaborator));
}
private class MyMockObjectTestCase extends MockObjectTestCase{}
private class MyNewClass{
public boolean executeJob(Collaborator collaborator){
collaborator.executeSomeImportantFunction();
return true;
}
}
}
|
Within the setUp() method, a new "testcase" is instantiated based on a private inner
class created to extend a jMock-CGLIB MockObjectTestCase object. This small workaround is necessary to keep the
entire test class an RMock TestCase object while wielding any jMock functionality. For example, you'll set jMock
expectations like testCase.once(), rather than like once() because the
TestClass object extends RMockTestCase.
A mock object based on a ClassB class is constructed and provided an expectation. You then use it to help
instantiate an RMock Collaborator mock object. The class under test is MyNewClass
class (shown here as a private inner class). Again, its executeJob() method receives a
Collaborator object and runs the executeSomeImportantFunction() method.
Listings 11 and 12 show the code for ClassA and ClassB, respectively.
ClassA is a simple class with no implementation, while ClassB shows minimal details
to illustrate the point.
Listing 11. The ClassA class
public class ClassA{}
|
This class is just a dummy class I use to reinforce the point that RMock is necessary for mocking classes in which constructors receive object parameters.
Listing 12. The ClassB class
public class ClassB{
public ClassB(){}
public String wierdMethod(){
return "failed";
}
}
|
The ClassB class' wierdMethod returns failed. This is important because the
class must shortly return another string for your test to pass.
Listing 13 shows the most important part of the test example: the Collaborator class.
Listing 13. The Collaborator class
public class Collaborator {
private String _string;
private ClassA _classA;
private ClassB _classB;
public Collaborator(String string, ClassA classA, ClassB classB) throws Exception{
_string = string;
_classA = classA;
if(classB.wierdMethod().equals("passed")){
_classB =classB;
}
else{
throw new Exception("Something bad happened");
}
}
public void executeSomeImportantFunction(){
}
}
|
Note, first and foremost, that you mocked the ClassB class using the jMock framework. With RMock, there's no
real way of extracting and using a proxy from the mock object to use it elsewhere in the test setUp() method. With
RMock, the proxy object appears only after the startVerification() method has been called. The advantage
in this case is with jMock because you can get what you need to set up other mock objects in case they need to return objects that are
themselves mocks.
The second thing to notice is that, conversely, you couldn't use the jMock framework to mock the Collaborator class.
The reason is that the class doesn't have a no-arguments constructor. Moreover, there is certain logic within its constructor that would determine
whether an instance can be obtained in the first place. In fact, for purposes of this discussion, the wierdMethod()
method in ClassB must return passed for a Collaborator object to be instantiated.
However, also note that by default, the method always returns failed. There is an obvious need to mock
ClassB for your test to be successful.
Also, unlike the earlier examples, a class array in this scenario is included as an extra parameter to the intercept()
method. It is not strictly necessary, but serves as a key to quickly identify which object classes you used in instantiating the RMock test object.
Run the new test case. This time, you'll see successful results. Figure 7 shows the happy ending.
Figure 7. Scenario 4 test success with RMock and jMock in collaboration
The Collaborator mock object is set up correctly, and the mockClassB object performs
as expected.
A quick look at the test tool differences
As you've seen throughout the scenarios, both jMock and RMock are powerful tools for testing Java code. However, there are always limitations with any other tools used in development and testing. Indeed, other testing tools are available, but none works as well (in Java technology) as RMock and jMock. Personal experience has shown me that the Microsoft® .NET framework carries some powerful tools, as well (such as TypeMock), but that's beyond the scope of this article and, indeed, the platform.
Table 1 shows some differences between the two frameworks and possible issues experienced over time, especially within the Eclipse environment.
Table 1. Differences between the RMock and jMock testing frameworks
| Test mocking styles | jMock | RMock |
|---|---|---|
| Can mock interfaces | Yes: New Mock() method | Yes: The mock() method |
| Can mock concrete classes | Yes: The mock() method with CGLIB | Yes: the mock() or intercept()
method |
| Can mock any concrete class | No: A no-arguments constructor must be present | Yes |
| Can obtain proxies anytime | Yes | No: Only after startVerification() ready state |
| Issues with other Eclipse plug-ins | No known issues | Yes: In-memory conflicts with the CoverClipse plug-in for Eclipse |
I encourage you to use these frameworks, given their power to produce results for unit testing. Many Java developers aren't used to writing tests frequently. More often than not, if they do write tests, they're typically very simple, covering the method's main functional aim. To test some of the "harder-to-get-to" sections of the code, jMock, and RMock are excellent choices.
Their use would drastically reduce bugs in code and improve your skill in using proven methods to test programming logic. Also, reading documentation and experimenting with the improved versions of these and other frameworks would be a great addition to your developer skills (and reduce any tolerance you may have had for poorly constructed code).
Learn
-
Getting started with jMock is a great place to get up and running with jMock.
-
RMock — a Java test-double framework is a well-formed guide to the RMock framework.
-
ServerSide.com's "Using
jMock with Test Driven Development" tutorial by Paulo Caroli provides an alternate jMock resource.
-
Browse Safari
Books online for books about jMock and RMock, as well as many other technical topics.
-
Check out the "Recommended Eclipse reading list."
-
Browse all the Eclipse content on developerWorks.
-
Users new to Eclipse should check out Eclipse project resources' Start Here.
-
Expand your Eclipse skills by checking out IBM developerWorks' Eclipse project resources.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
For an introduction to the Eclipse platform, see "Getting started with the Eclipse Platform."
-
Stay current with developerWorks' Technical events and webcasts.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Download the jMock testing framework for Java, a great testing resource for developers.
-
Download Freshmeat's jMock source, an alternate resource location for jMock.
-
The RMock testing framework for Java.
-
Download the Cobertura code coverage tool, a widely used tool
for code coverage.
-
Download the EclEmma Java code coverage tool, a stable code coverage tool for the Eclipse platform.
-
The JUnit testing framework for Java, the basic test resource for all Java developers.
-
Check out the latest Eclipse technology downloads at IBM alphaWorks.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
-
The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
-
The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
-
Participate in developerWorks blogs and get involved in the developerWorks community.
Comments (Undergoing maintenance)






