Thursday, February 02, 2006

Capturing parameters in EasyMock 2.0

I love EasyMock, especially the new version, 2.0, which really takes advantage of JDK 1.5 generics. Without EasyMock (1.1) I don't think either Tapestry or HiveMind would be as high quality as they are.

But even with the friendlier 2.0 version, there's one difficult case: When the code you are testing creates an object and passes it to another mock object. With the built in tools, you can use the isA() argument matcher to check its type, but that's about it.

In my situation, the code I was testing was creating an interceptor and pushing it onto the InterceptorStack. The interceptor wasn't returned, just pushed, and the InterceptorStack was itself a mock. I need to get that interceptor object so I can invoke methods on it, to ensure that it was created properly.

My solution was to create an EasyMock arguments matcher that exists just to capture a single parameter value. Here's the class:

package com.javaforge.tapestry.epluribus.tx;

import org.easymock.IArgumentMatcher;

import static org.easymock.EasyMock.reportMatcher;

/**
 * An argument matcher that captures an argument value. This allows an object created inside
 * a test method to be interrogated after the method completes, even when the object is not a return
 * value.
 * 
 * @author Howard M. Lewis Ship
 * @param <T>
 *            the type of object to capture
 */
public class Capture<T> implements IArgumentMatcher
{
    private T _captured;

    public void appendTo(StringBuffer buffer)
    {
        buffer.append("capture()");
    }

    @SuppressWarnings("unchecked")
    public boolean matches(Object parameter)
    {
        _captured = (T) parameter;

        return true;
    }

    public T getCaptured()
    {
        return _captured;
    }

    public static <T> T capture(Capture<T> capture)
    {
        reportMatcher(capture);

        return null;
    }

}

When asked to match an argument, it returns true after storing the captured value for later.

The static capture() method is important, it's how we thread things together. Here's an example from my test case:

  Capture capture = new Capture();
  InterceptorStack stack = createMock(InterceptorStack.class);

  . . .

  stack.push(capture(capture));

  . . .

  TransactionalService interceptor = capture.getCaptured();

Again, while I'm training the method invocations on my mock, I setup the capture, using the static capture() method. Later, after I've invoked my test class (the TransactionInterceptorFactory), I can get that interceptor object that was pushed onto the InterceptorStack and invoke more methods on it. Seems to work like a charm!

No comments:

Post a Comment

Please note that this is not a support forum for Tapestry. Requests for help will be deleted. Please subscribe to the Tapestry user mailing list if you are in need of support, or contact me directly for professional (for pay) support.

Spammers: Don't bother. I delete your comments and it's a waste of time for both of us. 垃圾邮件发送者:不要打扰。我删除您的评论和它的时间对我们双方的浪费