2013-07-17 12 views
9

Sto utilizzando un URL restful per avviare un processo di back-end di lunga durata (normalmente è su un cron schedule, ma vogliamo la possibilità di eseguirlo manualmente).Test di Spring asyncResult() e jsonPath() insieme

Il codice seguente funziona e vedo il risultato nel browser quando eseguo il test manualmente.

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

quando prova questo senza l'Callable ho usato il codice qui sotto e funziona tutto (ho cambiato il messaggio di errore previsto per semplificare 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")); 

Quando ho aggiunto il Callable però non funziona. Ho anche provato di seguito ma non ha funzionato. Qualcun altro ha avuto successo?

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

Di seguito è riportata la parte pertinente dalla mia stampa(). Sembra che mockMvc non possa sbrogliare correttamente Json in questo caso (anche se funziona nel mio browser)? Quando eseguo questa operazione senza Callable, vedo JSON completo.

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

risposta

16

risposta di Bud davvero mi ha aiutato a puntare nella direzione giusta ma non ha funzionato del tutto, perché non ha aspettato il risultato asincrona. Dopo aver postato questa domanda, gli esempi di vetrina mvc spring (https://github.com/SpringSource/spring-mvc-showcase) sono stati aggiornati.

Sembra che nella prima parte della chiamata quando si recupera MvcResult, è necessario specificare su asyncResult() e nel caso di mapping JSON pojo è necessario specificare sul tipo stesso (non JSON) . Quindi ho dovuto aggiungere la terza riga sotto alla risposta di Bud, quindi il resto funziona.

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

Nota:instanceOf() è org.hamcrest.CoreMatchers.instanceOf. Per accedere alle librerie di Hamcrest è incluso l'ultimo jar hamcrest-library.

Per Maven ...

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

Sarebbe opportuno sottolineare che il metodo 'instanceOf()' fa parte della libreria di Hamcrest. Mi ci è voluto un po 'per scoprirlo e poi inserire l'importazione ** maven ** appropriata. Date un'occhiata a questo esempio [mvc showcase] (https://github.com/spring-projects/spring-mvc-showcase/blob/master/src/test/java/org/springframework/samples/mvc/async/CallableControllerTests .java) aiutato. –

+0

Certo, buon punto. Aggiunto. –

+0

utilizza asyncDispatch() completamente necessario? Sembra interrompere l'incapsulamento del test - che dovrebbe solo sapere quale risultato aspettarsi quando invia un GET a/trigger/job/xyz - per sapere che l'implementazione di quel controller è asincrona –

5

Penso che si desidera utilizzare asyncDispatch sul risultato della Async preliminari Chiamate codice di riferimento dal link qui sotto

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

Uso comporta l'esecuzione di una richiesta prima che avvia l'elaborazione asincrona:

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

E quindi eseguire la distribuzione asincrona riutilizzando MvcResult:

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

o nel tuo caso

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

Impressionante! Grazie mille. Non riuscivo a trovarlo nei documenti ed era esattamente quello che cercavo. –

+0

Hmmm ... sembra che sia veramente asincrono e non aspetta come il test fallisce casualmente. –

5

risposta di Matt è corretta, ma vorrei perform a lavorare solo. Di seguito è riportato un metodo di esecuzione che è possibile utilizzare per testare entrambe le richieste di sincronizzazione e asincrono. Quindi non devi preoccuparti dei tuoi test su come il backend gestisce le richieste. Sei interessato solo alla risposta effettiva comunque, giusto?

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

Un modo per integrare che per i test è quello di mettere in una classe di base astratta comuni ed estendere le classi di test effettivi da esso:

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

quindi implementare i test estendendo la classe di base e usa il metodo perform. In questo esempio mockMvc è reso privato per guidare delicatamente tutti i futuri autori di test per utilizzare il metodo di esecuzione personalizzato.

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