Tip

Using JMock in test-driven development

Applying mock objects effectively is a key factor when performing test-driven development (TDD). In this article I'll introduce the basics of using JMock, a mock object framework, in conjunction with test-driven development.

Requires Free Membership to View

To illustrate the technique I will work through a case study, the creation of a cache component by means of test-first development with JMock.

Unit test and Mock Objects

Soon after the creation of the first unit test frameworks, a common need was identified and a complementary initiative began – mock objects. Mock objects help you design and test the relations between the objects entangling the whole system. You can think of mock objects as stubs that mimic the behavior of real objects in controlled ways. The process of identifying mock objects during test creation helps on defining objects interfaces. Thus developing with mock objects helps on building a loosely coupled - and therefore maintainable, reusable, and testable – system.

Typically an object-oriented (OO) application consists of a network of objects working together to accomplish a goal. Unit testing OO applications is a difficult task, especially when dealing with dependencies among objects. Figure 1 shows a hypothetical OO system.

Figure 1: A network of objects

Let’s analyze the effort of unit testing component A. For this purpose, Figure 2 attempts to create a visual representation for the Unit Test of component A – ATest. Such visual representation illustrates that ATest’s boundaries go beyond component A; somehow ATest also depends on components B and C.

Figure 2: ATest unit test

In an attentive change of ATest, let’s consider adding components B and C into the ATest unit test. Figure 3 shows a new version of ATest where its boundaries have been extended to components B and C.

Figure 3: ATest adding B and C

The new version of ATest, as depicted in Figure 3, adds B and C into ATest unit test. But once again ATest has more dependencies. Component C depends on component D. And the dependency chain could go further (D depends on E). The hypothetical ATest inclusive expansion would create a unit testing component A and all subsequent dependencies. Basically ATest should test component A only. It should be tangible and concise. A recommended alternative solution for ATest is to use stubs - mocks - of component B and C (Figure 4) instead of the real implementations of B and C.

Figure 4: ATest with mockB and mockC

As depicted in Figure 4, ATest does not include D – or D’s mock. As far as ATest is concerned A uses B and C. Therefore mock B and mock C are enough for effectively building ATest unit test.

Mock objects help isolate the component being tested from the components it depends on. It does so by providing a mechanism to create stubs of components and its expected behavior. This mechanism relies on the dependent components interface. By doing TDD with mock objects you will produce components which are independent of each other implementation. Instead components will dependent on each other’ interface. Figure 4 shows the representative replacement of components B and C by its equivalent mock B and mock C.

TDD

Test-driven development (TDD) is an evolutionary approach to development which instructs you to have test-first development intent. Basically, you start by writing a test and then you code to elegantly fulfill the test requirements.

The steps of TDD are overviewed in “The Steps of test-first development.” The first step is to quickly add or expand a unit test to your test suite. Next you run the test suite to ensure that the new test does in fact fail. You then update your functional code until it passes the test suite. Once the test suite does not fail, you should refactor the code; and then start over again.

Figure 5: TDD Steps

Refactoring is a development practice for restructuring an existing code, altering its internal structure without changing its external behavior. Refactoring keeps the code clean and easy to understand. Refactoring is safer in TDD because you always have the test required for validating the component, independently of its internal implementation. A good time for refactoring is after a test pass state, thus you can refactor and verify that the test is still passing.

Case study

The case study consists of following the TDD steps (as presented in Figure 5 ) for a cache component creation. If you are new to TDD and JMock you should follow this article linearly and make the presented code work in your development environment. Make sure to set-up your development environment with JUnit and JMock libraries. A good IDE can be of great help, especially in a TDD approach.

In the Mock Roles not Objects (PDF) article, the authors use a staged timed cache development to explain the article main concepts. The cache development is a very instructive example for introducing mock objects. But moreover, it provides a great sample for applying TDD. For these reasons, in this article, I also use the cache code as the basis of the case study. Whereas I present a new implementation for the TimedCache, and apply TDD to demonstrate each interaction until the development is complete. At the end of this case study you will have implemented TimedCache, its test component and the interfaces of the services used by the cache.

The case study is separated in 3 interactions following the TDD steps (figure 5) in the proposed order:

  1. write test for validating a new requirement
  2. write functional code fulfilling the test
  3. code refactoring

Interaction 1

Requirement

The cache component must load an object when lookUp() is invoked for the first time.

1.1 test code

TimedCacheTest, a unit test for TimedCache, is created prior to any functional code development – test first approach.

TimedCacheTest class

public class TimedCacheTest extends MockObjectTestCase {
    public void testLoadsObjectThatIsNotCached() {
        Mock mockLoader = mock(ObjectLoader.class);
        TimedCache cache = new TimedCache((ObjectLoader) mockLoader.proxy());
        Object KEY = new Object();
        Object VALUE = new Object();
        mockLoader.expects(once()).method("load").with(eq(KEY)).will(
            returnValue(VALUE));
        assertSame("should be first object", VALUE, cache.lookUp(KEY));
    }
}

Code Notes:

  • JMock and JUnit capabilities are made available to TimedCacheTest class as it extends MockObjectTestCase
  • The cache delegates the load capability to an ObjectLoader component, so in the cache test code the mock ObjectLoader is passed as a cache constructor parameter.
  • KEY and VALUE are created as Objects to be used in this test.
  • mockLoader is expected to be invoked once, returning VALUE for load(KEY) invocation.
  • assertSame(VALUE, cache.lookUp(KEY) is invoked

The cache development should not be dependent of the ObjectLoader code development nor its internal implementation details. TimedCacheTest unit test, as shown in Figure 6 , uses, a mockLoader to stub the ObjectLoader functionality. Applying mock objects is a great way to achieve loose coupling and identifying interface during your development. For example ObjectLoader interface has been identified while developing TimedCacheTest

Figure 6: TimedCacheTest unit test

The cache component takes the ObjectLoader dependency in its constructor on instantiation. This design is known Construction Dependency Injection (CDI). The Construction Dependency Injection makes possible for the cache to use an ObjectLoader component (a real implementation or a Mock object) without depending on its concrete implementation.

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached fails>>

The test code does not compile.

1.2 functional code

Following are the code necessary for fulfilling the test.

ObjectLoader interface

public interface ObjectLoader {
    public abstract Object load(Object Key);
}

Code Notes:

  • ObjectLoader provides the load method signature.

TimedCache class

TimedCache class is created with the simplest implementation that makes the test pass:

public class TimedCache {
    private ObjectLoader loader;

    public TimedCache(ObjectLoader loader) {
        this.loader = loader;
    }

    public Object lookUp(Object key) {
        return loader.load(key);
    }
}

Code Notes:

  • loader is a TimedCache private field injected by the constructor
  • The presented version of TimedCache.lookUp() method has the simplest implementation to pass the test – it only returns the loaded object

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached pass>>

1.3 Refactor

Following is a refactored version of the TimedCacheTest refactored.

public class TimedCacheTest extends MockObjectTestCase {
    private static final Object KEY = new Object();
    private static final Object VALUE = new Object();
    private Mock mockLoader ;
    private TimedCache cache;

    @Override
    protected void setUp() throws Exception {
        mockLoader = mock(ObjectLoader.class);
        cache = new TimedCache((ObjectLoader) mockLoader.proxy());
        super.setUp();
    }

    public void testLoadsObjectThatIsNotCached() {
        mockLoader.expects(once()).method("load").with(eq(KEY)).will(
            returnValue(VALUE));
        assertSame("should load the object", VALUE, cache.lookUp(KEY));
    }
}

Code Notes:

  • KEY and VALUE are created as constants
  • mockLoader and cache became private fields
  • setup() method is used to create the mock object and the cache.

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached pass>>

Interaction 2

Requirement

The cache component must load an object when lookup() is invoked for the first time.

The cache component must not reload objects that have been previously loaded.

2.1 test code

A new test for the presented requirement has been added to the test class.

TimedCacheTest class

...

    public void testDoesNotLoadObjectThatIsCached () {
        mockLoader.expects(once()).method("load").with(eq(KEY)).will(
            returnValue(VALUE));
        assertSame("should load the object", VALUE, cache.lookUp(KEY));
        assertSame("should get the cached object", VALUE, cache.lookUp(KEY));
    }
...

Code Notes:

  • mockLoader is expected to be invoked once, returning VALUE for load(KEY) invocation.
  • assertSame(VALUE, cache.lookUp(KEY) is invoked – first time
  • assertSame(VALUE, cache.lookUp(KEY) is invoked – second time

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached pass>>

<< TimedCacheTest.testDoesNotLoadObjectThatIsCached fails >>

The current cache implementation version does invoke the loader for each lookup() call. This is caught in the new unit test method - testDoesNotLoadObjectThatIsCached () . Basically the JMock expectation on mockLoader.load() method fails as it is invoked twice instead of once.

2.2 functional code

TimedCache class :

The following code enhances the TimedCache functionality, adding caching
capabilities, so an Object, if previously loaded, will not be reloaded.

public class TimedCache {
    private ObjectLoader loader;
    private Map<Object, Object> cachedValues = new Hashtable();

    public TimedCache(ObjectLoader loader) {
        this.loader = loader;
    }

    public Object lookUp(Object key) {
        Object value = (Object) cachedValues.get(key);
        if(value == null){
            value = loader.load(key);
            cachedValues.put(key, value);
        }
        return value;
    }
}

Code Notes:

  • cachedValues, a private Hashtable field, has been introduced to map key Objects to value Objects.
  • lookUp() method has been updated so Objects are cached after the first invocation and are only loaded if not available in cachedValues.

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached pass>>

<< TimedCacheTest.testDoesNotLoadObjectThatIsCached pass>>

Interaction 3

Requirement

The cache component must load an object when lookup() is invoked for the first time.

The cache component must not reload objects that have been previously loaded and have been on cache for too long.

The cache component must reload objects that have been previously loaded and have not been on cache for too long.

3.1 test code

A new method for the new requirement has been added to the unit test code.

The new requirement introduces the aspect of time, time elapsed, and a decision to determine if an object should be reloaded. A point in time is represented by Timestamp object. The ReloadPolicy component is responsible for deciding upon Objects reload.

Below you will find the modified code of the TimedCacheTest class.

TimedCacheTest class

...

    private static final Timestamp CURRENT_TIME = new Timestamp(System
        .currentTimeMillis());

    private static final Timestamp LOAD_TIME = CURRENT_TIME;

...

Code Notes:

  • CURRENT_TIME and LOAD_TIME are constants used on this test; they are both initialized with the same value as the cache test results does not depend on their values. Note that the expectations are deliberately set on the mock objects using CURRENT_TIME and LOAD_TIME .
...
    private Mock mockClock;

    private Mock mockReloadPolicy;

    protected void setUp() throws Exception {
        mockLoader = mock(ObjectLoader.class);
        mockClock = mock(Clock.class);
        mockReloadPolicy = mock(ReloadPolicy.class);
        cache = new TimedCache((ObjectLoader) mockLoader.proxy(),
        (Clock) mockClock.proxy(), (ReloadPolicy) mockReloadPolicy
            .proxy());
        super.setUp();
    }

...

Code Notes:

  • The cache delegates the reload policy verification to a ReloadPolicy component; mockReloadPolicy is injected as a cache constructor parameter.
  • The cache delegates the time retrieval capability to a Clock component; mockClock is injected as a cache constructor parameter.
...
    public void testCachedObjectsAreNotReloadedBeforeTimeout() {
        mockLoader.expects(once()).method("load").with(eq(KEY)).will(
            returnValue(VALUE));
        mockClock.expects(atLeastOnce ()).method("getCurrentTime")
            .withNoArguments().will(returnValue(CURRENT_TIME));
        mockReloadPolicy.expects(atLeastOnce ()).method("shouldReload").with(
            eq(LOAD_TIME), eq(CURRENT_TIME)).will(returnValue(false));
        assertSame("loaded object", VALUE, cache.lookUp(KEY));
        assertSame("cached object", VALUE, cache.lookUp(KEY));
    }
...

Code Notes:

  • mockLoader is expected to be invoked once, returning VALUE for load(KEY) invocation – the second invocation of lookup() should not invoke loader since the reloadPolicy is indicating that the object should not be reloaded.
  • mockClock is expected to be invoked at least once, returning CURRENT_TIME for getCurrentTime () invocation – the clock must be invoked for getting current time prior to questioning the reloadPolicy.
  • mockReloadPolicy is expected to be invoked at least once, returning false for shouldReload (LOAD_TIME, CURRENT_TIME) invocation.
  • assertSame(VALUE, cache.lookUp(KEY) is invoked – first time; VALUE Object should be loaded
  • assertSame(VALUE, cache.lookUp(KEY) is invoked – second time, VALUE Object should not be loaded because returnPolicy.shouldReload()returns false.

Figure 7 represents the unit test and the cache component using mockLoader, mockReloadPolicy and mockClock, which respectively stub the functionalities for ObjectLoader, ReloadPolicy and Clock.

Figure 7: TimedCachetTest unit test and required mock objects

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached fails>>

<< TimedCacheTest.testDoesNotLoadObjectThatIsCached fails >>

<< TimedCacheTest.testCachedObjectsAreNotReloadedBeforeTimeout fails >>

The test code does not compile because of the new changes.

3.2 functional code

Following are the code necessary for fulfilling the new test.

Clock interface

public interface Clock {
    Timestamp getCurrentTime();
}

Code Notes:

  • a Clock interface with the getCurrentTime() method
  • the usage of a clock component facilitates time-related tests; without the Clock component the cache unit test would get more complex and time consuming.

ReloadPolicy interface

public interface ReloadPolicy {
    boolean shouldReload(Timestamp loadTime, Timestamp currentTime);
}

Code Notes:

  • a ReloadPolicy interface with the shouldReload() method
  • a component implementing the ReloadPolicy interface must provide a shouldReload() method which makes the decision for reloading an Object based in the original load time and the current time.

TimedCache class

The following code segments enhance the TimedCache component, making the necessary changes for fulfilling the new requirement.

...
    private Clock clock;
    private ReloadPolicy policy;

    public TimedCache(ObjectLoader loader, Clock clock, ReloadPolicy policy) {
    this.loader = loader;
    this.clock = clock;
    this.policy = policy;
    }
...

Code Notes:

  • clock and ReloadPolicy are added as TimedCache private field injected by the constructor
  • The constructor is changed to add the Clock and ReloadPolicy parameters.
...

    private Map<Object, TimestampedValue> cachedValues = new Hashtable<Object, TimestampedValue>();();

    private class TimestampedValue{
        public Object value;
        public Timestamp loadTime;
    }

...

Code Notes:

  • TimestampedValue, a private inner class of TimedCache is created to hold the value object as well as the load time timestamp.
  • The value type (key/value) of the Hashtable is changed from Object to TimestampedValue.
...

    public Object lookUp(Object key) {
        TimestampedValue timestampedValue = (TimestampedValue) cachedValues.get(key);
        Timestamp currentTime =clock.getCurrentTime();
        if((timestampedValue == null)||((policy.shouldReload(timestampedValue.loadTime,currentTime)))){
            Object value = loader.load(key);
            timestampedValue = new TimestampedValue();
            timestampedValue.loadTime = currentTime;
            timestampedValue.value = value;
            cachedValues.put(key, timestampedValue);
        }
        return timestampedValue.value;
    }

...

Code Notes:

  • lookUp() method is changed to invoke clock and reloadPolicy for deciding upon current time and object reload possibility.
  • When caching the value object, the load time is marked on the timestampedValue before inserting to cachedValues

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached fails>>

<< TimedCacheTest.testDoesNotLoadObjectThatIsCached fails >>

<< TimedCacheTest. testCachedObjectsAreNotReloadedBeforeTimeout pass >>

Oops, the new test passed, but the previous ones failed.

Why did this happen?

The TimedCache.lookUp() method implementation has changed. Clock and ReloadPolicy are now being invoked.

JMock complains about any method invocation for which you haven't accounted for in advance with an expectation. Basically the previous tests have not been prepared for clock.getCurrentTime() and reloadPolicy.shouldReload() invocations.

TimedCacheTest class

Below is the code update for testLoadsObjectThatIsNotCached and testLoadsObjectIsNotCached unit test methods in order to set expectations correctly.

     public void testLoadsObjectThatIsNotCached() {
        mockLoader.expects(once()).method("load").with(eq(KEY)).will(
            returnValue(VALUE));
        mockClock.expects(once()).method("getCurrentTime").withNoArguments()
            .will(returnValue(CURRENT_TIME));
        mockReloadPolicy.expects(never()).method("shouldReload");
        assertSame("should load the object", VALUE, cache.lookUp(KEY));
    }

    public void testLoadsObjectIsNotCached() {
        mockLoader.expects(once()).method("load").with(eq(KEY)).will(
            returnValue(VALUE));
        mockClock.expects(exactly(2)).method("getCurrentTime")
            .withNoArguments().will(returnValue(CURRENT_TIME));
        mockReloadPolicy.expects(atLeastOnce()).method("shouldReload").with(
            eq(LOAD_TIME), eq(CURRENT_TIME)).will(returnValue(false));
        assertSame("should load the object", VALUE, cache.lookUp(KEY));
        assertSame("should get the cached object", VALUE, cache.lookUp(KEY));
    }

Code Notes:

  • mockClock expectation have been set accordingly
  • mockReloadPolicy expectation have been set accordingly

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached pass >>

<< TimedCacheTest.testDoesNotLoadObjectThatIsCached pass >>

<< TimedCacheTest.testCachedObjectsAreNotReloadedBeforeTimeout pass >>

3.3 Refactor

Once again all test scenarios are passing.

The last test – testCachedObjectsAreNotReloadedBeforeTimeout – ensures that objects are not reloaded if they have not timed out according to the reload policy. To make sure the cache and its reload policy are working properly we still need to test the case where objects that have timed out will be reloaded.

The following code adds this test case to TimedCacheTest class.

TimedCacheTest class

...

    private static final Object NEW_VALUE = new Object();

...

    public void testReloadsCachedObjectAfterTimeout() {
        mockClock.expects(atLeastOnce()).method("getCurrentTime")
            .withNoArguments().will(returnValue(CURRENT_TIME));
        mockLoader.expects(exactly(2)).method("load").with(eq(KEY)).will(onConsecutiveCalls(
             returnValue(VALUE),returnValue(NEW_VALUE)));
        mockReloadPolicy.expects(atLeastOnce()).method("shouldReload").with(
             eq(LOAD_TIME), eq(CURRENT_TIME)).will(returnValue(true));
        assertSame("should be loaded object", VALUE, cache.lookUp(KEY));
        assertSame("should be reloaded object", NEW_VALUE, cache.lookUp(KEY));
    }

Code Notes:

  • NEW_VALUE have been added as a constant required by this new unit test method.
  • mockClock is expected to be invoked at least once, returning CURRENT_TIME for getCurrentTime () invocation.
  • mockLoader is expected to be invoked twice, returning VALUE and NEW_VALUE, for consecutive load(KEY) invocations.
  • mockReloadPolicy is expected to be invoked at least once, returning true for shouldReload (LOAD_TIME, CURRENT_TIME) invocation.
  • assertSame(VALUE, cache.lookUp(KEY) is invoked – first time; VALUE Object should be loaded
  • assertSame(NEW_VALUE, cache.lookUp(KEY) is invoked – second time, NEW_VALUE Object is the reloaded value for the second invocation of lookup(KEY), which required a reload because returnPolicy.shouldReload()returns false.

Test Results

<< TimedCacheTest.testLoadsObjectThatIsNotCached pass>>

<< TimedCacheTest.testDoesNotLoadObjectThatIsCached pass >>

<< TimedCacheTest.testCachedObjectsAreNotReloadedBeforeTimeout pass >>

<< TimedCacheTest.testReloadsCachedObjectAfterTimeout pass >>

Conclusion

This article detailed a case study using mock objects in test-driven development. It included a full implementation, in a test-first style, of a cache component and its test case. You leveraged how mock objects are able to stub dependent components in a test code.. Furthermore, you observed the creation of a fully-tested, loosely coupled system with well defined interfaces as a result of following a staged TDD in conjunction with mock objects. The case study in this article uses Java, JUnit and JMock, but you can employ the same concepts and steps with other programming languages, unit tests and Mock object frameworks.

Paulo Caroli, www.caroli.org, is an application architect for ThoughtWorks, an outstanding application development and consulting company. Paulo has a master's degree in software engineering and is a Sun Certified Architect for Java Technology, but moreover, following ThoughtWorks leadership, has been successfully applying Agile techniques such as XP and TDD.

I would like to thank ThoughtWorks and its employees for their shared knowledge and practical Agile expertise, and especially offer my thanks to Paul Hammant, www.paulhammant.com, my mentor and co-worker at ThoughtWorks for reviewing this article.

This article originally appeared on TheServerSide.com.

This was first published in February 2007

There are Comments. Add yours.

 
TIP: Want to include a code block in your comment? Use <pre> or <code> tags around the desired text. Ex: <code>insert code</code>

REGISTER or login:

Forgot Password?
By submitting you agree to receive email from TechTarget and its partners. If you reside outside of the United States, you consent to having your personal data transferred to and processed in the United States. Privacy
Sort by: OldestNewest

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

Disclaimer: Our Tips Exchange is a forum for you to share technical advice and expertise with your peers and to learn from other enterprise IT professionals. TechTarget provides the infrastructure to facilitate this sharing of information. However, we cannot guarantee the accuracy or validity of the material submitted. You agree that your use of the Ask The Expert services and your reliance on any questions, answers, information or other materials received through this Web site is at your own risk.