Virtual Mock Objects using AspectJ with JUNIT

The authors show us how they use aspect-oriented programming in AspectJ to facilitate isolation of testable units, without hand-crafting Mock Objects or using a specialized Mock Object generation tool.

Contents:

Summary

True unit testing requires that you only test the unit itself and not any services that the unit may use from other units. In this paper we describe our experiences of using aspect-oriented programming techniques to isolate the unit under test.

Instead of hand-crafting or using a tool to generate code for Mock Objects, the approach we take uses AspectJ to intercept calls to “real” objects returning mock values that are set within the test itself.

Introduction

Unit testing, as well as forming part of the armory of techniques for improving reliability in may software processes has also become a key part of the new “Agile Methodologies” such as eXtreme Programming (XP)(1). In XP, automated unit tests for a piece of functionality are written before the functionality itself is written. To make this possible, a family of open source testing frameworks has grown up around the SUnit(2) tool originally developed by Kent Beck for Smalltalk. Of these, JUnit3 (the Java flavor of the tool) is perhaps the most used.

This concept of Test First Development provides developers with a suite of unit tests that can be run at any time to determine the overall health of the system. Thus any changes to the system that have unwanted side-effects become immediately apparent when tests start failing.

Figure 1 shows this parallel arrangement of classes and test classes. The example shows a LoginView (the class being tested) which as properties of userID and password that are set prior to calling a validate method. Following that call, the status attribute will be set to a string value that is ultimately displayed to the user. The LoginView is responsible for interpreting the status code (in the form of the integer constants USER_VALID, NO_PASSWORD, NO_USERID, INVALID_USER) that is returned from an AccessController, to determine the status message to display.

Figure 1. Unit Test Class

In this example system, we want to write four tests for the validate method of the LoginView class.

An example test might be:

public void testValidateValidUser()
{
  LoginView view = new LoginView();
  view.setUserID("jane");
  view.setPassword("passAugust");

  view.validate();

  assertEquals("login successful", view.getStatus());
}

The class being tested is instantiated, and then the method being tested is called. An assertion is then made about the results of that call. The status of the view should have been set to “login sucessful”.

However, the LoginView class makes use of the login service provided by the AccessController class. The AccessController will probably itself use other components to authenticate the user. For the tests to work it would be necessary to create some test users in the system. The tests that were written to test the behavior of LoginView would in fact also be testing the AccessController, which should of course have its own suite of tests.

The behavior we are trying to test here is to determine that the user gets the correct status message for the different cases of invalid or missing user credentials returned by the AccessController and not the actual validation of those credentials.

Mock Objects

The example above illustrates how difficult it often is to isolate a unit for which tests are needed from the other parts of the system. One approach to this is to use Mock Objects. Figure 2 shows the same example as Figure 1, but using a Mock object for the AccessController.

Figure 2. Mock Objects

The MockAccessController specializes the AccessController to be type compatible, but overrides the public methods to return hard-coded data. So, the mock login method might return a hard coded integer status code of INVALID_USER. An example of the sort of code that would be necessary is shown in Java below:

public int login(String userID, String password)
{
  return INVALID_USER;
}

There are a number of problems with this:

  • The LoginView class must be constructed in such a way that it can be supplied with a mock AccessController. Test considerations are now affecting the design.
  • For all the four different test cases to be exercised, it will be necessary for the mock login method to return different values depending on the username and password. This makes the method quite complex.

Virtual Mock Objects

To get around these problems, we have used AspectJ4 to provide “virtual” mock objects. No concrete mock objects are actually created, but rather AspectJ is used to weave interceptions into all code that is not part of the class under test. In this way, every method call is intercepted. If the method call is one for which mock data has been declared, then that mock data is returned in place of the actual method being run.

To illustrate this, the testValidateValidUser method would now become:

public void testValidateValidUser()
{
  LoginView view = new LoginView();
  Integer mockResult = new Integer(AccessController.USER_VALID);
  setMock("AccessController.login", mockResult);

  view.validate();

  assertEquals("login successful", view.getStatus());
}

From a testing point of view, this has a number of big advantages:

  • The test data that the test requires is defined in the test itself, not in some remote Mock object.
  • Different tests can set different mock data for the same method.
  • No actual Mock Objects need to be created, everything is contained in the test.
  • Only the component under test (LoginView) is being tested.

Figure 3. Virtual Mock Objects

The way that this frameworks works is that the setMock method adds an association between the method and the mock data to the hashtable. This hashtable is shared by the MethodInterceptor aspect which intercepts every method call, checks to see if mock data is defined for it, and if it is, returns the mock data rather than invoke to method itself. If no mock data is defined for the method, then the method is invoked as normal.

In Practice

We have deliberately presented a simplified view of the actual implementation of this mechanism. In practice, the system was somewhat more complex than this. In particular, a number of new assertion methods were added to our specialisation of TestCase:

  • assertCalled(methodName), which simply asserts that a particular method had been called during the invocation of the method under test.
  • assertNotCalled(methodName), the converse of the above.
  • assertArgumentPassed(methodName, argumentName, argumentValue), this is similar to assertCalled, but the values of arguments supplied were also captured so that assertions can be made about the values passed to methods of other components providing services to the method under test.

A number of other features were also developed:

  • Queue of mock values. Sometimes, the method under test will use the same service from another component more than once. To allow different mock values to be returned for each invocation, the setMock method adds values to a queue of return values in the hashtable.
  • Exceptions. To be able to simulate the throwing of exceptions by components providing services to the method under test, a parallel set of functionality was provided. So for example the method setMockException(method, exception) could be used to simulate the throwing of an exception. In the same way that the mock data is created in the test, the mock exception would also be instantiated and passed as an argument to setMockException.

Conclusions

This framework has proved very useful in developing true unit tests in a multi-tier system.

The only area where it has proved problematic is where the method under test uses a large number of other services from other components, that for some reason must all be mocked out. This resulted in some test methods requiring dozens of lines of setMock statements, making the test confusing and difficult to get right. Under these extreme circumstances, it was also difficult to think ‘test first’ because there was always a need to guess what was going to be mocked out. These problems occurred less when the components providing the services were developed before the component under test.

The full code for this framework is available from http://www.shopwriter.co.uk/simon/aspectj/

Acknowledgements

I would like to thank Frank Westphal and Michael Feathers for pointing me in this direction.

References

1. K. Beck, eXtreme Programming Explained: Embrace Change, Addison Wesley, Reading, Mass., 2000.

2. K. Beck, http://www.xprogramming.com/testfram.htm

3. JUnit, http://www.junit.org

4. AspectJ, http://www.aspectj.org

Authors and Contact Information

Simon Monk, Memote Ltd, UK
Stephen Hall, Lancaster University, UK

Posted on:

Written by: Ron Jeffries

Categorization: Articles

Recent Articles

XProgNew – Working with layout

I’ve been trying to learn some things about how the new implementation will handle the front page. The front page has a top row the most recent articles in two categories, “Beyond Agile” and “The Annals of Kate Oneal”, and …

XProgNew – today’s problem

With Bill Tozier, I’m working on re-basing XProgramming.com from WordPress to Ruby/Sinatra etc. We tweeted a bit about an issue we ran into today. It goes like this:

The image we had of the new site is that each article …