2013-07-17 10 views
9

Korzystam ze spokojnego adresu URL, aby uruchomić długo działający proces backendu (zwykle jest to harmonogram crona, ale chcemy go ręcznie uruchomić).Testowanie Spring asyncResult() i jsonPath() razem

Poniższy kod działa i widzę wynik w przeglądarce podczas testu ręcznego.

@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); 
     } 
    }; 
} 

Kiedy przetestować bez Callable Użyłem poniższy kod i wszystko działa (zmieniłem oczekiwany komunikat o błędzie w celu uproszczenia post).

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

Kiedy dodałem Callable jednak to nie działa. Próbowałem również poniżej, ale to nie zadziałało. Ktoś jeszcze miał sukces?

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"))); 

Poniżej znajduje się odpowiednia część z mojego wydruku(). Wygląda na to, że mockMvc nie może w tym przypadku poprawnie rozłączyć Json (nawet jeśli działa w mojej przeglądarce)? Kiedy robię to bez Callable widzę pełny 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 = [email protected] 


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 = [] 

Odpowiedz

16

odpowiedź Buda naprawdę mi punkt w dobrym kierunku, jednak pomogło to nie dość pracy, ponieważ nie czekać na wynik asynchroniczny. Od czasu opublikowania tego pytania zaktualizowano próbki pokazów wiosna-mvc (https://github.com/SpringSource/spring-mvc-showcase).

Wydaje się, że w pierwszej części wywołania, gdy pobierasz MvcResult, musisz potwierdzić na asyncResult(), a w przypadku mapowania pojo JSON musisz potwierdzić na samym typie (nie JSON) . Musiałem więc dodać trzecią linię poniżej do odpowiedzi Buda, a reszta po prostu działa.

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")); 

Uwaga:instanceOf() jest org.hamcrest.CoreMatchers.instanceOf. Aby uzyskać dostęp do bibliotek Hamcrest należy wymienić najnowszy słoik hamcrest-library.

Dla Maven ...

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

Dobrze byłoby zaznaczyć, że metoda 'instanceOf()' jest częścią biblioteki Hamcrest. Zajęło mi trochę czasu, aby znaleźć to, a następnie umieścić odpowiedni ** maven ** import. Spójrz na ten przykład [mvc showcase] (https://github.com/spring-projects/spring-mvc-showcase/blob/master/src/test/java/org/springframework/samples/mvc/async/CallableControllerTests .java) pomógł. –

+0

Jasne, dobry punkt. Dodany. –

+0

używa całkowicie asyncDispatch()? Wydaje się, że przełamanie enkapsulacji testu - powinno po prostu wiedzieć, jakiego rezultatu oczekiwać, wysyłając GET do/trigger/job/xyz - aby wiedzieć, że implementacja tego kontrolera jest asynchroniczna –

5

myślę chcesz użyć asyncDispatch na skutek rozpoczął Async wywołuje Kod referencyjny z linku poniżej

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

Wykorzystanie polega na przeprowadzeniu jednego żądania pierwszy rozpoczyna przetwarzanie asynchronicznego:

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

A następnie wykonanie asynchronicznej wysyłki ponownie przy użyciu MvcResult:

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

lub w przypadku

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")); 
+0

Awesome! Dziękuję bardzo. Nie mogłem tego znaleźć w dokumentach i to było dokładnie to, czego szukałem. –

+0

Hmmm ... wygląda na to, że jest naprawdę asychroniczny i nie czeka, ponieważ test losowo zawodzi. –

5

odpowiedź Matta jest poprawna, ale chciałbym perform po prostu pracować. Poniżej przedstawiono metodę wykonywania, której można użyć do testowania żądań asynchronizacji i synchronizacji. Nie musisz więc przejmować się testami, w jaki sposób backend obsługuje żądania. Jesteś zainteresowany rzeczywistą odpowiedzią, tak?

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; 
    } 
} 

Jednym ze sposobów, aby zintegrować że do testów jest, aby umieścić go we wspólnym abstrakcyjnej klasy bazowej i przedłużyć swoje rzeczywiste klas testowych z niego:

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; 
    } 
    } 
} 

Następnie realizować swoje testy, rozszerzając klasę bazową i użyj metody perform. W tym przykładzie polecenie mockMvc jest traktowane jako prywatne, aby łagodnie kierować wszystkimi przyszłymi autorami testów , aby użyć metody niestandardowego wykonania.

@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")); 
    } 
} 
Powiązane problemy