ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Approaches to Mocking

by Simon Stewart
02/11/2004

Everyone knows what a mock is, just from the name, but as with many seemingly simple ideas, there is more to them than first meets the eye. This article explores the two types of mocks that exist and covers some of the problems inherent in their use. Finally, it considers the reason why a developer might chose to use mocks. After all, common understanding holds that mocks are used for unit testing, a key part of Test Driven Design, but that isn't necessarily about testing at all.

One of the benefits to arise from the ascendancy of Extreme Programming (XP) is a greater emphasis on unit testing. Axiomatic to the success of unit tests is the fact that they are meant to be very fine-grained, ideally dealing with each method on a single object in turn. This is fine in theory, but how is a developer meant to isolate each method from its surrounding environment?

The common solution to this problem is to make use of Mock Objects, or "mocks" as they are often called. Mocks are stub-like objects that implement no logic of their own and are used to replace the parts of the system with which the unit under test interacts. Their behavior is controlled entirely by the unit test itself, and so they provide a simple mechanism for not only testing that code works when everything is "as expected," but also when unusual errors occur. For example, simulating a network failure can be automated properly using mock objects and will no longer require a swift tug on the network cable!

In the wild, mock objects may appear to roam in many different flavors, gently grazing upon the verdant expanse of developer mindshare, but all of these flavors can be broken down into two broad camps: static and dynamic mocks. A static mock object is either written by hand or generated automatically at some stage in a program's development (possibly at compile time), while dynamic mocks either rely on a proxy interface, provided by java.lang.reflect.Proxy or byte code manipulation, or alternatively, some sort of Aspect-Oriented Programming (AOP) magic.

To help illustrate the types of mock objects out there, imagine that you are developing an authorization framework that relies on a user manager component (ignoring existing projects, because reinventing the wheel is always fun). One method in the authorization framework might return a Boolean to indicate whether a user with a given user name and password can log in. It might look a little like this:

public boolean canUserLogIn( String username,
                             String password ) {
    UserManager um = UserManagerFactory.getInstance();
    User user = um.getUser( username );
    if (user != null) {
        if (user.validatePassword( password ))
            return true;
    }
    return false;
}

Since this has clearly not been developed "test first," the question is: how does one write unit tests that properly exercise this method, using mock objects?

Using Static Mocks

As it stands, the method is going to be extremely difficult to test using static mocks, because there's no easy way of ensuring that they get used without having extremely verbose unit tests. The patterns and practices that are adopted to make unit testing simpler, such as Inversion of Control (IoC) (popularized in the Avalon project) and refactoring can be used here in order to create some code better-suited to unit testing. The method could be refactored by placing the call to obtain an instance of the UserManager within a separate method which can be overridden in an inner class in the test (a technique documented in this article). However, shorter tests can be created by implementing the IoC pattern and obeying the Law of Demeter. This results in code that looks like this:

public void setUserManager( UserManager um ) {
    this.um = um;
}

public boolean canUserLogIn( String username,
                             String password ) {
    User user = um.getUser( username );
    return canUserLogIn( user, password );
}

public boolean canUserLogIn( User user,
                             String password ) {
    if (user == null)
        return false;

    return user.validatePassword( password );
}

The focus of our testing now becomes the canUserLogin( User, String ) method, for which a MockUser class will probably be useful. In simple cases, writing this class by hand can be a good idea, with a couple of extra methods tacked on to the class to allow its behavior to be controlled by the unit test itself:

public class MockUser implements User {
    ...

    // Setup what to return when validatePassword is called
    public void setValidatePasswordResult( boolean result ) {
        expectedCalls++;
        this.returnResult = result;
    }

    // Mock implementation of validatePassword
    public boolean validatePassword( String password ) {
        actualCalls++;
        return returnResult;
    }

    public boolean verify() {
        return expectedCalls == actualCalls;
    }

    ...
}

When writing mocks by hand, a verify method written like this would probably not be added, but an analogue to it is frequently created automatically in static mocks that have been generated by a tool.

Writing mocks by hand can be tedious. In order to make it easy for other people to make use of the mock class, a naming convention should be followed. When the interface or class being mocked has numerous methods, this can become repetitive and dull, and therefore prone to careless errors, especially if the programmer is not infallible. XP lore suggests only writing unit tests for things that can possibly go wrong, so if you happen to have an infallible developer hanging around, ask her to write the actual application!

All of this cries out for a code-generation tool, and the world is awash with them. MockMaker and MockDoclet are two popular examples; both of these rely on a doclet tag being placed in the Javadocs for the class to be mocked, and then running a pre-processor to generate the mocks. In addition, MockMaker can integrate with a number of popular Java IDEs, which makes things even simpler and helps to avoid the problem of an overly helpful IDE offering to create "missing" classes.

Code generation automatically causes mocks to use certain naming conventions. If the code generation is built into the build process, the mocks automatically mirror any changes in the interface they represent, again removing a source of error and confusion.

Using a static mock approach and the hand-coded MockUser listed above, a sample unit test could be:

public void testCanUserLogin() {
    MockUser user = new MockUser();
    user.setValidatePasswordResult( true );

    boolean result = um.canUserLogin( user,
                                  "foobar" );

    assertTrue("Expected to validate using " +
              "password \"foobar\"",
              result );
    assertTrue("Mock User not used as expected",
              user.verify();
}

So what's the catch? It's something that many of the mock frameworks share: poor implementations or an incorrect understanding of how to use a mock object package can lead to brittle unit tests. This is because it's common to set "expectations" on the mock, indicating, for example, that there should be a call to validatePassword and that this should have the parameter foobar. Often, the mocks are generated so that this implicitly suggests that there should be at most one call to this method. As a result, should there be another call to the validatePassword method, it may cause the test to break. In essence, the unit test stops treating the method as a black box and starts to attempt to mirror the call sequence within the method. Overall, the unit tests become poorer, since the developer has now stopped testing the contract the method makes with the rest of the code. As a contrived example, the test would fail if the method being tested had been changed like so:

public boolean canUserLogIn( User user,
                             String password ) {
    if (user == null)
        return false;

    // Note the extra call to validatePassword
    if (!user.validatePassword( password ))
        log.info( user.getUsername() + " tried to " +
                  "login without a valid password" );

    return user.validatePassword( password );
}

There's another catch, too. The mock objects must be completely accurate in what they return. This is fine when the mocks are modelling objects within your own code, but not when they are used to model something from another package. For example, if a mock assumes that javax.sql.DataSource.getConnection() returns null if the database connection fails, when it actually throws an SQLException, then although the unit tests may pass, the method being tested relies on incorrect assumptions and may therefore fail in unexpected ways.

Pages: 1, 2

Next Pagearrow