Тестирование Spring asyncResult() и jsonPath() вместе

Я использую релаксирующий URL, чтобы запустить длительный бэкэнд-процесс (обычно он выполняется по расписанию cron, но мы хотим иметь возможность запускать его вручную).

Код ниже работает, и я вижу результат в браузере, когда я тестирую вручную.

@ResponseBody
@RequestMapping(value = "/trigger/{jobName}", method = RequestMethod.GET)
public Callable<TriggerResult> triggerJob(@PathVariable final String jobName) {

    return new Callable<TriggerResult>() {
        @Override
        public TriggerResult call() throws Exception {
            // Code goes here to locate relevant job and kick it off, waiting for result
            String message = <result from my job>;
            return new TriggerResult(SUCCESS, message);
        }
    };
}

Когда я проверяю это без Callable Я использовал код ниже, и все это работает (я изменил ожидаемое сообщение об ошибке, чтобы упростить публикацию).

mockMvc.perform(get("/trigger/job/xyz"))
    .andExpect(status().isOk())
    .andDo(print())
    .andExpect(jsonPath("status").value("SUCCESS"))
    .andExpect(jsonPath("message").value("A meaningful message appears"));

Когда я добавил Callable Однако это не работает. Я также попробовал ниже, но это не сработало. У кого-нибудь еще был успех?

mockMvc.perform(get("/trigger/job/xyz"))
    .andExpect(status().isOk())
    .andDo(print())
    .andExpect(request().asyncResult(jsonPath("status").value("SUCCESS")))
    .andExpect(request().asyncResult(jsonPath("message").value("A meaningful message appears")));

Ниже соответствующая часть из моей печати (). Похоже, mockMvc не может правильно распутать Json в этом случае (даже если он работает в моем браузере)? Когда я делаю это без Callable Я вижу полный JSON.

MockHttpServletRequest:
     HTTP Method = GET
     Request URI = /trigger/job/xyz
      Parameters = {}
         Headers = {}

         Handler:
            Type = foo.bar.web.controller.TriggerJobController
          Method = public java.util.concurrent.Callable<foo.bar.myproject.web.model.TriggerResult> foo.bar.myproject.web.controller.TriggerJobController.triggerJob(java.lang.String)

           Async:
 Was async started = true
      Async result = foo.bar.myproject.web.model.TriggerResult@67aa1e71


Resolved Exception:
            Type = null

    ModelAndView:
       View name = null
            View = null
           Model = null

        FlashMap:

MockHttpServletResponse:
          Status = 200
   Error message = null
         Headers = {}
    Content type = null
            Body = 
   Forwarded URL = null
  Redirected URL = null
         Cookies = []

3 ответа

Решение

Ответ Бада действительно помог направить меня в правильном направлении, однако он не совсем сработал, потому что не ожидал асинхронного результата. После публикации этого вопроса образцы spring-mvc-showcase ( https://github.com/SpringSource/spring-mvc-showcase) были обновлены.

Кажется, что в первой части вызова, когда вы получаете MvcResult, вы должны утверждать asyncResult(), а в случае отображения pojo JSON вам нужно утверждать сам фактический тип (не JSON). Поэтому мне нужно было добавить третью строку ниже к ответу Бада, тогда остальное просто работает.

MvcResult mvcResult = this.mockMvc.perform(get("/trigger/job/xyz"))
    .andExpect(request().asyncStarted())
    .andExpect(request().asyncResult(instanceOf(TriggerResult.class)))
    .andReturn();

this.mockMvc.perform(asyncDispatch(mvcResult))
    .andExpect(status().isOk())
    .andExpect(content().contentType(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("status").value("SUCCESS"))
    .andExpect(jsonPath("message").value("A meaningful message appears"));

Замечания: instanceOf() является org.hamcrest.CoreMatchers.instanceOf, Чтобы получить доступ к библиотекам Hamcrest, включите последние hamcrest-library баночка.

Для мавена...

    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-library</artifactId>
        <version>LATEST VERSION HERE</version>
        <scope>test</scope>
    </dependency>

Ответ Мэтта правильный, но я бы хотел perform просто работать. Ниже приведен метод выполнения, который можно использовать для проверки как асинхронных, так и синхронизированных запросов. Таким образом, вам не нужно заботиться о том, как сервер обрабатывает запросы. В любом случае вас интересует только фактический ответ, верно?

ResultActions perform(MockHttpServletRequestBuilder builder) throws Exception {
    ResultActions resultActions = mockMvc.perform(builder);
    if (resultActions.andReturn().getRequest().isAsyncStarted()) {
      return mockMvc.perform(asyncDispatch(resultActions
          .andExpect(request().asyncResult(anything()))
          .andReturn()));
    } else {
      return resultActions;
    }
}

Один из способов интегрировать это в ваши тесты - поместить его в общий абстрактный базовый класс и расширить из него ваши действительные тестовые классы:

import static org.hamcrest.Matchers.anything;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml")
public abstract class AbstractMockMvcTests {

  @Autowired
  protected WebApplicationContext wac;

  private MockMvc mockMvc;

  @Before
  public void setup() throws Exception {
    mockMvc = webAppContextSetup(this.wac).build();
  }

  protected ResultActions perform(MockHttpServletRequestBuilder builder) throws Exception {
    ResultActions resultActions = mockMvc.perform(builder);
    if (resultActions.andReturn().getRequest().isAsyncStarted()) {
      return mockMvc.perform(asyncDispatch(resultActions
          .andExpect(request().asyncResult(anything()))
          .andReturn()));
    } else {
      return resultActions;
    }
  }
}

Затем реализуйте свои тесты, расширив базовый класс и используя метод execute. В этом примере mockMvc сделан приватным, чтобы осторожно указывать всем будущим авторам тестов использовать пользовательский метод выполнения.

@RunWith(SpringJUnit4ClassRunner.class)
public class CallableControllerTests extends AbstractMockMvcTests {

  @Test
  public void responseBodyAsync() throws Exception {
    perform(get("/async/callable/response-body"))
      .andExpect(status().isOk())
      .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
      .andExpect(content().string("Callable result"));
  }

  @Test
  public void responseBodySync() throws Exception {
    perform(get("/sync/foobar/response-body"))
      .andExpect(status().isOk())
      .andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
      .andExpect(content().string("Sync result"));
  }
}

Я думаю, что вы хотите использовать asyncDispatch на результат запущенных асинхронных вызовов Ссылочный код по ссылке ниже

http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.html

Использование предполагает выполнение одного запроса первым, который запускает асинхронную обработку:

 MvcResult mvcResult = this.mockMvc.perform(get("/trigger/job/xyz"))
        .andExpect(request().asyncStarted())
        .andReturn();

А затем выполняя асинхронную диспетчеризацию, повторно используя MvcResult:

 this.mockMvc.perform(asyncDispatch(mvcResult))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andExpect(content().string(.......));

или в вашем случае

this.mockMvc.perform(asyncDispatch(mvcResult))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andExpect(jsonPath("status").value("SUCCESS"))
        .andExpect(jsonPath("message").value("A meaningful message appears"));
Другие вопросы по тегам