开发者

Conditionally simulating exceptions/return values when testing Axis 1.4 SOAP calls

Apologies for the longwinded question...

In order to test business logic which calls Axis 1.4 web services in my unit tests, I'm currently using a Spring proxy which allows me to set exceptions and simulate return values as per below.

However, I was wondering whether there was a cleaner way of doing this:

In my JUnit application context I introduce the following advice:

<bean id="testProxy" class="appl.service.TestProxyImpl" />
<bean id="testAdvice" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
    <constructor-arg index="0" ref="testProxy"/>
    <constructor-arg index="1" value="appl.service.ITestProxy"/>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="myAxisProxy"/>
    <property name="interceptorNames" value="testAdvice"/>
</bean>

The TestProxyImpl class is as follows (let me know if you need to see the interface):

public class TestProxyImpl 
    extends DelegatingIntroductionInterceptor 
    implements ITestProxy {

    private Map<String, Integer> calls = new HashMap<String, Integer>();
    private Map<String, Throwable[]> exceptions = new HashMap<String, Throwable[]>();
    private Map<String, Object> returnValues = new HashMap<String, Object>();
    private Map<String, Object[]> lastParams = new HashMap<String, Object[]>();

    public int getCalls(String methodName) {
        Integer noCalls = calls.get(methodName);
        if (noCalls == null) {
            return 0;
        }
        return noCalls;
    }

    public void resetCalls() {
        calls.clear();
        returnValues.clear();
        exceptions.clear();
        lastParams.clear();
    }

    public void addExceptions(String method, Throwable... exceptions) {
        this.exceptions.put(method, exceptions);
    }

    public void addReturnValue(String method, Object result) {
        returnValues.put(method, result);
    } 

    @SuppressWarnings("unchecked")
    public <T> T getLastParameter(String method, Class<? extends T> paramClass) {
        Object[] args = lastParams.get(method);
        if (args != null) {
            for (Object arg : args) {
                if (arg != null) {
 开发者_开发百科                   if (paramClass.isAssignableFrom(arg.getClass())) {
                        return (T)arg;
                    }
                }
            }
        }
        return null;
    }

    @Override
    protected Object doProceed(MethodInvocation mi) throws Throwable {
        String methodName = mi.getMethod().getName();
        int noCalls;
        synchronized (calls) {
            noCalls = getCalls(methodName);
            calls.put(methodName, noCalls + 1);
        }
        Object[] args = mi.getArguments();
        synchronized (lastParams) {
            lastParams.put(methodName, args);
        }
        if (exceptions.containsKey(methodName)) {
            Throwable[] exceptionArray = exceptions.get(methodName);
            if (exceptionArray != null) {
                Throwable e = exceptionArray[noCalls % exceptionArray.length];
                if (e != null) {
                    throw e;
                }
            }
        }
        if (returnValues.containsKey(methodName)) {
            return returnValues.get(methodName);
        }
        return super.doProceed(mi);
    }
}

which allows me to use the following unit tests:

protected void onSetUp() throws Exception {
    super.onSetUp();

    // testProxy is autowired into the unit test class
    testProxy.resetCalls();
}

public void testSuccess() throws Exception {

    // myBusinessObject is autowired into the unit test class
    // myAxisProxy is injected into myBusinessObject in the spring context
    // myBusinessObject calls myAxisProxy.myMethod(MyRequestObject) internally
    myBusinessObject.doSomething();

    // check that myAxisProxy has been called the correct times
    // with the correct parameters
    assertEquals(1, testProxy.getCalls("myMethod"));
    assertEquals(
        "foo", 
        testProxy.getLastParameter(
            "myMethod", 
             MyRequestObject.class).getMyField());
 }

 public void testWithoutCallingProxy() throws Exception {

     testProxy.addReturnValue("myMethod", createTestReturnValues());

     myBusinessObject.doSomething();

     // check that the response is as expected
 }

 public void testWithException() throws Exception {
     testProxy.addException(
         "myMethod", 
         AxisFault.makeFault(new ConnectException("test")),
         null);

     // this would check that the first call results in a ConnectException
     // simulating a network error, but myBusinessObject retries
     // and goes through to the service on the second call.
     myBusinessObject.doSomething();
 }

I've briefly looked into EasyMock, but was not sure how I'd be able to create a mock object that intercepts the call and returns an exception or specified return values some of the time, but uses the correct call some other times, so was wondering whether anyone had ideas on how to simplify this.

Note, ideally I'd like to avoid having to rewire the properties of myBusinessObject.

Thank you.

EDIT:

I'm using Spring 2.5, JUnit 3.8 and Axis 1.4

EDIT2:

I'm using this approach to automate integration tests, rather than simple unit tests, so I'm any suggestions on how to replace the handcrafted solution with something based on a library are most welcome.


Typical layered development should be organized something like below:

Data Access

Business Logic

Service Interface

This again should remind that when developing repeatable independent unit tests that your tests should target logic and functionality in its intended layer and component ONLY. Thus when testing Business Logic I do not want to make actual service calls at all, I don't even want to make Data Access calls either, that is why we use Mocking frameworks like EasyMock.

If you don't like EasyMock then try looking at Mockito. It has a few more features for dealing with code that is less than ideal for mocking.

And I find it strange that you would go to so much trouble to avoid a little rewiring. It is well worth the refactoring effort.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜