Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Unit Testing Spring Framework’s @Async Annotation

DZone's Guide to

Unit Testing Spring Framework’s @Async Annotation

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.

I had a task to make my current webservice calls asynchronous and wanted to use the @Async annotation in Spring 3.0x to accomplish the task.

The @Async annotation can be provided on a method so that invocation of that method will occur asynchronously. In other words, the caller will return immediately upon invocation and the actual execution of the method will occur in a task that has been submitted to a Spring TaskExecutor. In the simplest case, the annotation may be applied to a void-returning method.

This blog is going to detail how I accomplished unit testing this service.


The new @Async annotation from SpringSource version 3.0.x can be found on http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/scheduling.html section 25.5.2

The first thing that stumped me when creating this poc, is that the Object that is annotated, cannot be the came Object that is executed as a Callable<V>.

First I take my existing component I want to change from serial, to asynchonous

public interface AppointmentComponent {     public XMLGregorianCalendar getAppointmentDate(String accountNumber); }

Then the actual implmentation was this

public XMLGregorianCalendar getAppointmentDate(String accountNumber) {    logger.debug("getAppointmentDate");     XMLGregorianCalendar appointmentDate = null;     try {        AppointmentRequest request = AppointmentAssembler.assembleAppointmentRequest(accountNumber);        AppointmentResponse response = client.lookupAppointment(request);         if (response.getAppointment() != null) {            appointmentDate = response.getAppointment().getAppointmentDate();            logger.debug("found appointment date of {}.", appointmentDate);        } else {            appointmentDate = DatatypeFactory.newInstance().newXMLGregorianCalendar();            appointmentDate.setYear(BigInteger.ZERO);        }     } catch (Exception ex) {        logger.error("DAS returned an Exception ", ex.getMessage());    }    return appointmentDate;}

Then I created an Async version Interface

public interface AppointmentComponentExecutor {     public Future<XMLGregorianCalendar> getAppointmentDate(String accountNumber);     public XMLGregorianCalendar getFromFuture(Future<XMLGregorianCalendar> futureAccountInfo);     public void setAppointmentComponent(AppointmentComponent component); }

then the implementation was in 2 parts.

  1. I wanted to make the asynchronous call, and get Future<V> back to the caller.
  2. Next I wanted to provide a way to get the response object from the Future<V> and wrap the exceptions.


public class AppointmentComponentExecutorImpl implements AppointmentComponentExecutor {     @Autowired    public AppointmentComponent appointmentComponent;     // FIXME need to externalize this property.    static long timeout = 20L;     private static final Logger logger = LoggerFactory.getLogger(AppointmentComponentExecutorImpl.class);     @Async    public Future<XMLGregorianCalendar> getAppointmentDate(String accountNumber){        logger.debug("@Async getAppointmentDate(accountNumber)");        XMLGregorianCalendar appointmentDate = appointmentComponent.getAppointmentDate(accountNumber);        return new AsyncResult<XMLGregorianCalendar>(appointmentDate);    }     public XMLGregorianCalendar getFromFuture(Future<XMLGregorianCalendar> futureAccountInfo){        logger.debug("getFromFuture");         XMLGregorianCalendar appointmentDate = null;        try {            appointmentDate = futureAccountInfo.get(timeout, TimeUnit.SECONDS);        } catch (TimeoutException e) {            // TODO retry logic?        } catch (Exception e) {            logger.error("Async request returned an exception: ", e.getMessage());        }         return appointmentDate;    }     public void setAppointmentComponent(AppointmentComponent appointmentComponent){        this.appointmentComponent = appointmentComponent;    }}

I wanted to ensure I could get some form of Unit Test done, so I used JUnit and Mockito. I mocked out my component.

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = {"classpath:application-context-test.xml"})public class AppointmentComponentExecutorTest {     @Autowired    private AppointmentComponentExecutor componentExecutor;     private AppointmentComponent component;     protected AppointmentResponse response = new AppointmentResponse();     static final long timeout = AppointmentComponentExecutorImpl.timeout;     protected String accountNumber;     protected XMLGregorianCalendar appointmentDate;     @Before    public void setup() {        component = Mockito.mock(AppointmentComponent.class);         componentExecutor.setAppointmentComponent(this.component);        accountNumber = "1113034566667890";    }     @Test    public void testGetAppointmentDateAsync() throws Exception {         final Stopwatch stopwatch = new Stopwatch();         final XMLGregorianCalendar appointmentDate = AppointmentDateFixture                .createDateFutureAppointment();         when(component.getAppointmentDate(accountNumber))                .thenAnswer(MockUtils.getThreadDelayAnswer(appointmentDate, 20));         stopwatch.start();        long beforeAsyncCall = stopwatch.stop();         System.out.println("******* beforeAsyncCall: " + beforeAsyncCall);         Future<XMLGregorianCalendar> futureApptDate = componentExecutor                .getAppointmentDate(accountNumber);         long afterAsyncCall = stopwatch.stop();         System.out.println("******* afterAsyncCall: " + stopwatch.stop());         // Should be returned before the delayed timeout.        assertThat(futureApptDate.isDone(), is(false));         XMLGregorianCalendar dateReturned = futureApptDate.get();         long afterFutureGet = stopwatch.stop();        System.out.println("******* afterFutureGet: " + afterFutureGet);         assertThat(futureApptDate.isDone(), is(true));        verify(component).getAppointmentDate(accountNumber);         // should be the total time it takes to finish the async call.        assertThat(afterFutureGet, is(greaterThan(afterAsyncCall + 10L)));    }     @SuppressWarnings("unused")    @Test(expected = TimeoutException.class)    public void testGetAppointmentDate_Timeout() throws Exception {         // Should take 40ms        when(component.getAppointmentDate(accountNumber)).thenAnswer(                MockUtils.getThreadDelayAnswer(appointmentDate, 40));         Future<XMLGregorianCalendar> futureApptDate = componentExecutor                .getAppointmentDate(accountNumber);         XMLGregorianCalendar result = futureApptDate.get(5L, TimeUnit.MILLISECONDS);        fail();    }     @Test    public void testGetAppointmentDate_Null_Date() throws Exception {         when(component.getAppointmentDate(any(String.class)))                .thenReturn(null);         Future<XMLGregorianCalendar> futureApptDate = componentExecutor                .getAppointmentDate(accountNumber);         futureApptDate.get();     }     @Test    public void testGetFromFuture() throws Exception {         Future<XMLGregorianCalendar> future = mock(Future.class);         when(future.get(timeout, TimeUnit.SECONDS))                .thenReturn(appointmentDate);         XMLGregorianCalendar result = componentExecutor.getFromFuture(future);         verify(future).get(timeout, TimeUnit.SECONDS);     }     @Test    public void testGetFromFuture_TimeoutException() throws Exception {         Future<XMLGregorianCalendar> future = mock(Future.class);         when(future.get(timeout, TimeUnit.SECONDS))                .thenThrow(                        new TimeoutException());         XMLGregorianCalendar result = componentExecutor.getFromFuture(future);         assertNull(result);         verify(future).get(timeout, TimeUnit.SECONDS);     }    @Test    public void testGetFromFuture_ExecutionException() throws Exception {         Future<XMLGregorianCalendar> future = mock(Future.class);         // throws java.util.concurrent.ExecutionException()        when(future.get(timeout, TimeUnit.SECONDS))                .thenThrow(                        new RuntimeException());         XMLGregorianCalendar result = componentExecutor.getFromFuture(future);         assertNull(result);         verify(future).get(timeout, TimeUnit.SECONDS);     }}

Then I create aMock Helper to aid in creating a Mockito Answer delay.

mport org.mockito.invocation.InvocationOnMock;import org.mockito.stubbing.Answer; public class MockUtils {     @SuppressWarnings("rawtypes")    public static Answer getThreadDelayAnswer(final Object o, final int delay) {        return new Answer() {            public Object answer(InvocationOnMock invocation) {                try {                    Thread.sleep(delay);                } catch (InterruptedException e) {                    //ignore this error.                    //Will happen while running unit tests.                    //e.printStackTrace();                }                return o;            }        };    }}

The stopwatch implmentation is just a simple timer I have been using

public class Stopwatch {     private long start;    private long stop;     public Stopwatch() {}     public void start() {        start = System.currentTimeMillis();    }     public long stop() {        stop = System.currentTimeMillis();        return stop - start;    }}

NOTE: There is an issue with Session Scoped Beans and @Async. Here is the error:

11:31:58,917 11/04 741CE4739B8E1BC7A87CA317155100C9 ERROR IdentifyComponentImpl - DAS returned an Exception:org.springframework.remoting.RemoteAccessException: Could not access remote service at [http://pacdcdtadeva03:8050/DAS010WSWAR/services/IdentifyService]; nested exception is javax.xml.ws.WebServiceException: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.callSession': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. 

https://jira.springframework.org/browse/SPR-6479

I will post a further blog about this issue, but not now.

Conclusion

The @Async is a very handy and easy to use annotation that hide much of the details of auto-proxying asynchronous calls. The downside, is that it hides the complexity, and when issues arise, might be difficult to track down. Also there is the mentioned issue trying to proxy requests that rely on Session scoped Objects.

 

From http://www.baselogic.com/blog/development/java-javaee-j2ee/unit-testing-springframework-s-async-annotation

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.

Topics:
java

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}