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

advertisement

AddThis Social Bookmark Button

Approaches to Mocking
Pages: 1, 2

Another problem is to decide where to draw the line between when to write the mocks by hand and when to start generating them. Do you "go automatic" from the start? When the class being mocked has a rapidly changing API? When an object has more than n methods on it? Perhaps when there's a "y" in the month name? Or maybe something equally arbitrary? If the mocks are hand-written, then care must be taken to ensure that they are so simple that they don't need to have their own unit tests. Yet if the mocks are generated, steps must be taken to ensure that the unit tests do not become too brittle. It's a balancing act that can be tough to keep up.



A final disadvantage is that generating the mocks at build time using some sort of doclet-driven approach can slow the build considerably, especially when the source contains numerous classes. While this may not appear to be a problem to start with, a slow build provides enormous discouragement for running the unit tests often, something that is an anathema to the test infected.

Assuming you're pretty comfortable with the idea of static mocks, let's consider some of the pros and cons of using them.

The Pros of Static Mocks

  • Type safety. Java is a strongly typed language, and mock objects should take advantage of polymorphism where possible to exploit this.

  • Simplicity. All Java developers should be comfortable writing a simple mock object by hand, and while the initial setup for a code generator might be troublesome, most of them integrate nicely with ant. So once it's up and running, it should just "tick along" without any intervention.

The Cons of Static Mocks

  • Generated mocks might confuse some IDEs, which promptly flag a number of unnecessary warnings for missing classes.

  • Writing mocks by hand quickly loses its appeal and can be error-prone.

  • Generating mocks automatically can lead to brittle test cases and slow builds.

  • It can be hard to tell when to use hand-crafted mocks, or when to generate them automatically.

Using Dynamic Mocks

As mentioned at the beginning of the article, dynamic mocks come in two general types: those that operate through some kind of proxy, and those that use AOP to intercept calls to the actual objects that shall be used in the deployed code. The former are far more prevalent, since they are simply an extension of the ideas that have already been explored with static mocks.

Although there may be dozens of packages that provide support for dynamic mocks, two that are widely used are Dynamocks (now part of the "jMock" package) and Easy Mock, and both of these rely on the java.lang.reflect.Proxy interface. Because of this, they can only be used to mock up interfaces, but this is hardly the limitation it could be if a sensible interface/implementation approach to the code's design has been taken. If it would really cause trouble, there are patches to make Dynamocks use CGLib to allow mocking of classes too. The documentation for Easy Mock is fairly well fleshed out, and so might be a good choice for people new to the concept, but using Dynamocks is hardly taxing:

public void testCanUserLogin() {
    com.mockobjects.dynamic.Mock mockUser =
        new Mock( User.class );
    mock.expectAndReturn( "validatePassword",
        C.args( C.eq( "foobar" ) ), true );

    boolean result = canUserLogin(
        (User) mockUser.proxy(), "foobar" );

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

Even though the setup of the dynamic mock is easy to perform, the extra complexity that it has introduced to the test setup makes the test harder to read and understand. For tests of more complex methods, this setup process can take some time and be hard to follow.

Dynamocks sidestep the issue of "missing classes" encountered with static mocks generated at compile time, but this String based approach can lead to problems too — what if the method name changes? Refactoring support in tools such as Eclipse or IDEA may help, but there's always going to be something that they miss, meaning that more development time than is necessary is spent fixing the unit tests after a simple refactoring; not an ideal use of resources.

Easy Mock's approach is different and takes the form of recording a series of calls against the interface being implemented and then checking that the correct calls are received. As a related concept, Easy Mock's MockControl.createNiceControl method allows the expectations to be set up, but ignores the number of times a method is called amongst other benefits, resolving one of the issues present in many of the static mock packages. One disadvantage of Easy Mock's approach is that things get a little ugly if the method being tested has to return a value or throw an Exception, since these are recorded on another, entirely different, object.

Finally, dynamic mocks can lead to more verbose unit tests. While a hand-written mock object might be hard-coded to return a sensible default value for each method, the same cannot be true of a proxy-based mock. Instead, should the need for a default return value arise, the mock needs to have these associated with certain method calls before it is used, and this is normally done in the test. Fortunately, the two packages considered here support this kind of usage, but it does limit the reuse offered by the mock objects themselves.

Dynamic mocks share some of the same advantages and disadvantages of static mocks — some provide for type safety, while it can still be hard to determine when to use them — but they can offer the following advantages and disadvantages.

The Pros of Dynamic Mocks

  • No need to rely on preprocessing or static files.
  • Can remove the sometimes lengthy generation of mocks from the build process without substantially changing how long it takes to run the unit tests.

The Cons of Dynamic Mocks

  • String-based dynamic mocks can lead to trouble when method names are refactored, while interface-based dynamic mocks suffer from some clumsiness when setting up return values.
  • Current dynamic mock packages are unable to mock anything but interfaces without patches being applied.
  • Can be hard to reuse code because of the way that default return values are set up.
  • Test set up becomes longer, reducing how readable a test is and making it harder to understand what the test is doing.

Using AOP For Mocking

While "making testing easier" is entirely the wrong reason for refactoring working code, anecdotal evidence suggests that testing with mocks leads to cleaner and better-composed code than code that is harder to test. Despite this, sometimes it is better to just test the method "as is," without any rewriting. Traditional approaches to unit testing will fail at this point, but fortunately, there's hope in the form of AOP. While this is a relatively new approach, there are articles that cover it available for the reader keen to experiment. Both of these articles point out many of the advantages that this approach offers, and concentrate on using AspectJ, although a similar result may be obtained using CGLib, since all that is required is an around advice.

One caveat is that not all AOP frameworks allow the developer to work with classes that aren't aware of the AOP package, which limits the potential application of this type of mock object. Of course, approaches based on AspectJ do not suffer from this, but methods based on CGLib will. The other obvious downside to using AOP is that it introduces an entirely new programming paradigm to an aspect of code writing that should be as staid and solid as possible — debugging unit tests, as mentioned before, is a pointless waste of time. Also, because aspects can be "woven" into the code at any point, there is less incentive to clean up code to make testing easier.

The Pros of AOP

  • Enormously flexible.
  • Can provide an easy route to unit-testing code that may not be refactored for some reason, such as performance or because of a lack of authority.

The Cons of AOP

  • Less incentive to refactor code to make it more manageable.
  • AOP is not familiar to the majority of developers, meaning that the unit tests are more prone to errors.
  • This method may not work with all AOP packages.

Deciding To Use Mock Objects

Bear in mind that not every lock should be opened with a hammer. Mock objects are a powerful weapon in a unit tester's arsenal, but sometimes it's best not to use them. There are certain tests where mock objects are definitely not a good fit, the most obvious being acceptance and integration tests. While unit tests allow a developer to ensure that an object's methods conform to a certain contract with the rest of the code, acceptance and integration tests are meant to test a system "end to end." Replacing chunks of this system with an idealized version is not the best way to get accurate test results!

Similarly, mocks are frequently a poor choice for replacing infrastructure. As mentioned above, writing a mock object implies a complete understanding of how the object being mocked is meant to behave, and infrastructure is generally something that is complex enough to make this a non-trivial task. Instead, perhaps it is better to focus on what the infrastructure is being used for, and replace that with a mock implementation, an idea explored by this blog entry. Of course, if the infrastructure that the unit test is collaborating with has suitable lightweight implementations, then there may be an argument to use those rather than relying on mocks at all.

While reviewing this article, Marty Andrews pointed out an important insight: there are two ways to use a mock object, either as a testing technique or as a design technique, and the intended use helps shape the available choices.

As a testing technique, mocks leave a lot to be desired because it's far too easy to fall into the trap of testing how a method produces its output, rather than testing the contract the object being tested presents to the rest of the world. This is not to say that mocks have no place being used as a testing technique, but simply to suggest that care should be taken to avoid this problem. If a developer finds herself mocking up the APIs of third-party libraries, then it may be time to step back and think about why the mocks are being used in the first place. Despite this drawback, mocks provide a great way for exercising parts of the code that are seldom touched by other types of testing.

As a design technique, mock objects can be used in conjunction with Test Driven Development (TDD) to help shape the code. For example, although mocking infrastructure is a bad idea, mocking the services that the infrastructure provides is a good idea. Providing an abstraction that encapsulates the services offered by infrastructure helps lead to clearer code and cleaner classes with better-defined responsibilities.

Deciding when to use which type of mock can be a difficult task. Some believe that using anything but hand-crafted mocks implies some sort of "code smell" — a problem with the code itself. Others believe that static mocks are primitive and a poor choice when dynamic mocks can provide so much flexibility and need not suffer the drawbacks of a String-based approach. The use of AOP-based mocks is probably something that will be mandated by the nature of the code that needs to be tested. However, while it may be fun to use the latest bleeding-edge technologies, there is little that AOP-based methods provide that cannot be offered by more traditional dynamic mock approaches and the application of a little lateral thinking.

Ultimately the approach used for providing and using mocks has to be determined by the developer, bearing in mind the size of the project, whether the codebase is already extant, and how much authority the developer has to make changes to the code, as well as how familiar they are with the techniques that each type of mock use, be they static, proxy-based or AOP-based.

Simon Stewart is a freelance developer currently working in Sydney.


Return to ONJava.com.