Joe Walnes
  Blog



Recent Entries

Creative uses of Hamcrest matchers

Hamcrest 1.1 released

Testing on the Toilet

Building testable AJAX apps (Does my button look big in this?)

QDox is back - 1.6 released

Java and .NET RESTful interoperability with XStream

I've joined Google

OSCon: SiteMesh, SiteMesh, SiteMesh, SiteMesh

Flexible JUnit assertions with assertThat()

SiteMesh and Content Management @ O'Reilly OpenSource Conference

XStream 1.1.2 released. Java 5 Enums, JavaBeans, field aliasing, StAX, and more...

VB.Net is the bestest

XStream 1.1.1 released

Accessing generic type information at runtime

XStream 1.1 released

JUnit tip: Setting the default timezone with a TestDecorator

XStream: how to serialize objects to non XML formats

How my backflip went...

Backflippin' in 4 hours.

Is 100% test coverage a BAD thing?

Looking back at the SiteMesh HTML parser

The road ahead for SiteMesh 3

Joe's Backflipping for Autistic Research - time is nearly up...

SiteMesh 2.2 Released

Advanced SiteMesh

More... [RSS | RDF]

About Joe Walnes

I am a software engineer for Google, based in London.

Open Source

WebStuff (coming soon)

XStream

ActiveMQ

SiteMesh

QDox

nMock

jMock

Pico Container

Nano Container

OpenSymphony

Squiggle

MockDoclet

MockObjects

Jelly

Groovy

PatternStitcher

XJB

Books

Java Open Source Programming, Wiley JSP Site Design, Wrox

Talks

Mock Roles, not Objects
October 26 2004, Vancouver, Canada. OOPSLA'04

Personal Development Practices Map
June 24 2004, Salt Lake City, Utah. Agile Development Conference

SiteMesh.NET and ASP.NET MasterPages
May 20 2004, Bangalore, India. Bangalore .NET User Group

Mock Objects: Driving Top Down Development
March 29 2004, St Neots, UK. OT2004

Mock Objects
December 2 2003, London, UK. XP Day 3


MockDoclet

MockDoclet is no longer maintained. But fear not, there are better things :
  • XDoclet - the original MockDoclet templates have been merged back into XDoclet.
  • MockMaker now contains an Ant task.
  • MockObjects contains a convenient system for building flexible mocks using dynamic proxies (no code generation required!).

What is MockDoclet?

MockDoclet is an active code generation tool for automatically generating mock objects implementation classes from existing classes or interfaces in Java.

What are Mock Objects?

Mock objects are used to assist unit testing. A mock object is passed into an object to be tested and allows testing to occur from the inside.

For example, suppose you have a servlet that looks at parameters from an HttpServletRequest object and runs some SQL against a database passed in as a JDBC Connection based on the parameter. This is quite awkward to test as you require your class to be deployed in a servlet engine and have a database available in order to test it. Even then, you have to establish an HTTP request to the server and check that the database has been updated correctly - how do you do that?

Mock objects provide an elegant solution to this problem. You supply a MockHttpServletRequest and MockConnection to your servlet. Mocks provide 2 useful pieces of functionality in this problem:

  • Firstly, you can easily setup a mock object with the necessary values it should return when methods are called. In this case, you could setup the MockHttpServletRequest to return certain parameters that you wish to test your servlet can handle.

  • Secondly, you can define how you expect your object to interact with the mock. In this case, our expectations are that the correct SQL is passed to our database, in relation to the parameters from the MockHttpServletRequest. If the servlet doesn't meet our expectations, the unit tests will fail.

More information...

What aren't Mock Objects?

Small animals with three legs of different lengths that run around mountains in the Highlands. Those are haggis.

But why another Mock Object framework?

Here's a quick description of the existing mock object frameworks, and how they relate to Mocklet.

  • MockObjects
    The MockObjects project aims to provide some utility classes for building mock objects and mock implementations of common libraries (such as IO, Servlets, JDBC and some vendor specific tools). It also serves as a site for describing usage patterns and hosting a community around mock objects. MockDoclet generated code depends on the MockObjects package.

  • MockMaker
    MockMaker achieves exactly the same results as MockDoclet except goes about it in a different way. MockMaker was designed to be a passive code generation tool whereas MockDoclet uses active code generation. See below for the difference. MockDoclet is based on the MockMaker tool and generates nearly identical source code. If you change your preference from passive to active or vice-versa, it should be pretty easy to switch around.

  • EasyMock
    EasyMock aims to achieve the same as MockDoclet and MockMaker except it does not generate code. Instead it makes use of reflection using dynamic proxies to automatically take on an interface at runtime. EasyMock is great for simple objects but can get tricky for more complicated interfaces. EasyMock complements MockMaker/MockDoclet nicely.

How does it work?

MockDoclet is built on top of XDoclet which is a generic code generation tool that uses JavaDoc to parse source files and extract JavaDoc comments like @this.

Any source file that defines @mock:generate in its JavaDoc header shall automatically have the source for a MockObject class added to it. The generated source file uses the MockObjects library internally.

The generate classes can then be compiled as normal and used in your unit tests.

Why is active code generation important?

The fundamental difference between MockDoclet and MockMaker is that MockDoclet is designed to be used as an active code generator and MockMaker as a passive code generator.

Passive code generators are wizard-like code generators that developers typically generate code once (with certain options) and then from then onwards treat the file as any other source file. It is the developer's responsibility to ensure it is maintained as the project evolves. Of course, generated code is generally not as clean and maintainable as developers would like them to be and can end up causing maintenance nightmares (MockModified syndrome).

Active code generators generate disposable code. The idea is that you have an input configuration that is easily maintainable and whenever it changes all the generated code is regenerated, overwriting previous versions. This is much simpler to maintain as the developer never actually modifies (or even looks at) the generated code.

Excellent coverage of code generators, including the pros and cons of active and passive generators, is given in the book The Pragmatic Programmer. Recommended read.

What do I need to use it?

Your project must be built using Ant and you need JUnit as your test framework. Some modifications and extra libraries are required at build time, and one extra jar is required when actually running your unit tests.

How do I use it?

You need to include a target in your build file to occur before compilation. This target will use MockDoclet to load the source files and generate any extra source files needed. The example below assumes your source files are in src and the generated source files are to be put in build/src.

[build.xml]
<!-- Path to mockdoclet and dependencies -->
<path id="mockdoclet">
  <fileset dir="lib/mockdoclet/" />
</path>

<target name="buildmocks">
  <!-- Load mockdoclet taskdef -->
  <taskdef name="mockdoclet"
       classname="org.mockmaker.doclet.MockDocletTask"
       classpathref="mockdoclet" />
  <mkdir dir="build/src" />
  <mockdoclet sourcepath="src" destdir="build/src" classpathref="mockdocklet">

    <!-- Process all src files -->
    <fileset dir="src">
      <exclude name="**/test/*" />
    </fileset>

    <!-- Generate Mock Object classes -->
    <mockobjects />

  </mockdoclet>
</target>

Next you markup your interfaces that you want Mock Objects generated for using @mock:generate.

[Database.java]
/**
 * Simple database abstraction.
 *
 * @mock:generate
 */
public interface Database {

  /**
   * Execute some SQL that modifies the database (UPDATE, INSERT, DELETE...).
   */
  void execute( String sql );

  /**
   * Execute some SQL that returns rows (SELECT...).
   */
  Rows select( String sql );

  // more methods...

}
[Rows.java]
/**
 * Simple database resultset abstraction.
 *
 * @mock:generate
 */
public interface Rows {

  boolean hasNext();
  String getString( String columName );

  // more methods...

}

Run ant buildmocks and it will generate MockDatabase and MockRows.

Now we have our mock objects, we can start testing code that depends on these above classes.

[AccountCleaner.java]
public class AccountCleaner {

  private Database database;

  public AccountCleaner( Database database ) {
    this.database = database;
  }

  public void fixCreditFlags() {
    Rows rows = database.select( "SELECT account, balance, credit FROM accounts" );
    database.start();
    while( rows.hasNext() ) {
      long account = rows.getLong( "account" );
      long balance = rows.getLong( "balance" );
      boolean credit = rows.getBoolean( "credit" );
      if ( balance < 0 && credit ) {
        database.execute( "UPDATE accounts SET credit = false WHERE account = " + account );
      }
      else if ( balance >= 0 && ! credit ) {
        database.execute( "UPDATE accounts SET credit = true WHERE account = " + account );
      }
    }
    database.commit();
  }

}
[AccountCleanerTest.java]
// more methods...

public void testBalances() {

  // check correct query is used for database.select().
  MockDatabase database = new MockDatabase();
  database.addExpectedSelectValues( "SELECT account, balance, credit FROM accounts" );

  // return some dummy data from database.select().
  MockRows rows = new MockRows();
  setupAccountRow( rows, 123, 999, true ); // correct
  setupAccountRow( rows, 124, -999, false ); // correct
  setupAccountRow( rows, 125, -1, true ); // should be corrected to false
  setupAccountRow( rows, 126, 5, false ); // should be corrected to true
  rows.setupHasNext( false );
  database.setupSelect( rows );

  // these are the expectations that need to be met to pass the test.
  database.addExpectedExecuteValues( "UPDATE accounts SET credit = false WHERE account = 125" );
  database.addExpectedExecuteValues( "UPDATE accounts SET credit = true WHERE account = 126" );

  // execute method to be tested.
  AccountCleaner cleaner = new AccountCleaner( database );
  cleaner.fixCreditFlags();

  // check it met our expectations.
  database.verify();

}

private void setupAccountRow( MockRows rows, long account, long balance, boolean credit ) {
  rows.setupHasNext( true );
  rows.setupGetLong( account );
  rows.setupGetLong( balance );
  rows.setupGetBoolean( credit );
}

// more methods...

Where do I get MockDoclet?

Download Mockdoclet

Tell me more!


- Joe Walnes <joe@truemesh.com>

ThoughtBloggers

Martin Fowler

Dan North

Aslak Hellesoy

Darren Hobbs

Geoff Oliphant

Mike Roberts

Chris Stevenson

Jon Tirsen

Loads More...

Agile Bloggers

Ken Arnold

Ward Cunningham

Brian Marick

Robert Martin

Bret Pettichord

Java Bloggers

Ara Abrahamian

Mike Cannon-Brookes

Vincent Massol

Bob McWhirter

Rickard Oberg

Joseph Ottinger

James Strachan

Hani Suleiman

Communities

eXtreme Tuesday Club (XTC)

Thursday GeekSpeek

ThoughtWorks GeekNight

London Java Meetup

The Codehaus

[RSS | RDF]
© 2001-2004, Joe Walnes

Powered by SiteMesh and Moveable Type.