Skip to content

MockMvc can't be test under multiple threads. [SPR-10838] #15464

@spring-projects-issues

Description

@spring-projects-issues

Kent Yeh opened SPR-10838 and commented

First, TestDispatcherServlet can't using single key to intercept multiple threads.

final class TestDispatcherServlet extends DispatcherServlet {
    private static final String KEY = TestDispatcherServlet.class.getName() + ".interceptor";
    private CountDownLatch registerAsyncInterceptors(final HttpServletRequest servletRequest) {
        //A unique key isn't suitable for multiple threads.
        asyncManager.registerCallableInterceptor(KEY, new CallableProcessingInterceptorAdapter() {
         ...
    }
    ...

Second, TestDispatcherServlet need override doDispatch() to sign dispatch process completed.
So, my solution is

  • Add CountDownLatch member to DefaultMvcResult
class DefaultMvcResult implements MvcResult {
    private CountDownLatch dispatchLatch;
    public CountDownLatch getDispatchLatch() {...}
    public void setDispatchLatch(CountDownLatch dispatchLatch) {...}
    ...
}
  • Change TestDispatcherServlet KEY to automic and override doDispatch() to notify process completed.
final class TestDispatcherServlet extends DispatcherServlet {
    private static final AtomicInteger idx = new AtomicInteger(0);
    private CountDownLatch registerAsyncInterceptors(final HttpServletRequest servletRequest) {

        final CountDownLatch asyncResultLatch = new CountDownLatch(1);
        //A CountDownLatch to offer doDispatch() a chance to sign that dispatch process completed.
        getMvcResult(servletRequest).setDispatchLatch(new CountDownLatch(1));

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
        //Make key atomic.
        String key = String.format("%s.%d", KEY, idx.getAndIncrement());
        asyncManager.registerCallableInterceptor(key, new CallableProcessingInterceptorAdapter() {
                ...
        });
        asyncManager.registerDeferredResultInterceptor(key, new DeferredResultProcessingInterceptorAdapter() {
        });
        return asyncResultLatch;
    }

    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        super.doDispatch(request, response);
        DefaultMvcResult mvcResult = getMvcResult(request);
        //Sign for process completed.
        if (mvcResult != null && mvcResult.getDispatchLatch() != null) {
            mvcResult.getDispatchLatch().countDown();
        }
    }
  • Finally override MockMvc perform()'s ResultActions to wait dispatch process completed.
public final class MockMvc {
    public ResultActions perform(RequestBuilder requestBuilder) throws Exception {
        ....
        return new ResultActions() {
            ...
            @Override
            public MvcResult andReturn() {
                if (mvcResult.getDispatchLatch() != null) {
                    try {
                        HttpServletRequest request = mvcResult.getRequest();
                        long timeout = request.getAsyncContext() == null ? defaultTimeout : request.getAsyncContext().getTimeout();
                        mvcResult.getDispatchLatch().await(timeout - delayResult, TimeUnit.MILLISECONDS);
                        Thread.sleep(delayResult);
                    } catch (InterruptedException ex) {
                    }
                }
                return mvcResult;
            }
        }
    }
    ...

Attatchment contain two projects that one raise error and another rewrited(source) run succees.


Affects: 3.2 GA

Attachments:

Issue Links:

Referenced from: commits 119e793, 072e5e8, ed9b296

3 votes, 10 watchers

Metadata

Metadata

Assignees

Labels

in: testIssues in the test moduletype: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions