2012-02-23 10 views
28

Sto scrivendo uno unit test per un metodo che contiene la seguente riga:Unità testare un metodo di dipendenti al contesto richiesta

String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId(); 

ottengo il seguente errore:

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.

La ragione è abbastanza ovvio: non sto eseguendo il test in un contesto di richiesta.

La domanda è: come posso testare un metodo che contiene una chiamata a un metodo dipendente dal contesto della richiesta in un ambiente di test?

Grazie mille.

risposta

30

È possibile simulare/arrestare l'oggetto RequestAttributes per restituire ciò che si desidera e quindi chiamare RequestContextHolder.setRequestAttributes(RequestAttributes) con il proprio mock/stub prima di iniziare il test.

@Mock 
private RequestAttributes attrs; 

@Before 
public void before() { 
    MockitoAnnotations.initMocks(this); 
    RequestContextHolder.setRequestAttributes(attrs); 

    // do you when's on attrs 
} 

@Test 
public void testIt() { 
    // do your test... 
} 
+1

Grazie @ nicholas.hauschild. Soluzione perfetta e pulita! – satoshi

+0

@nicholas, grazie per la soluzione rapida, ha funzionato per me, grazie ancora una volta – Bravo

1

Supponendo che la classe è qualcosa di simile:

class ClassToTest { 
    public void doSomething() { 
     String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId(); 
     // Do something with sessionId 
    } 
} 

Se non si ha la capacità di cambiare la classe che utilizza RequestContextHolder, allora si potrebbe sostituire la classe RequestContextHolder nel codice di prova. I.e. crei una classe con lo stesso nome, nello stesso pacchetto, e assicurati che venga caricata prima della classe Spring effettiva.

package org.springframework.web.context.request; 

public class RequestContextHolder { 
    static RequestAttributes currentRequestAttributes() { 
     return new MyRequestAttributes(); 
    } 

    static class MyRequestAttributes implements RequestAttributes { 
     public String getSessionId() { 
      return "stub session id"; 
     } 
     // Stub out the other methods. 
    } 
} 

Ora, quando i test eseguiti, prenderanno in mano la vostra classe RequestContextHolder e l'uso che di preferenza a primavera uno (assumendo che il percorso di classe è predisposto per questo accada). Questo non è un modo particolare per eseguire i test, ma potrebbe essere necessario se non è possibile modificare la classe che si sta testando.

In alternativa, è possibile nascondere il recupero dell'ID di sessione dietro un'astrazione. Per esempio introdurre un'interfaccia:

public interface SessionIdAccessor { 
    public String getSessionId(); 
} 

Creare un'implementazione:

public class RequestContextHolderSessionIdAccessor implements SessionIdAccessor { 
    public String getSessionId() { 
     return RequestContextHolder.currentRequestAttributes().getSessionId(); 
    } 
} 

e utilizzare l'astrazione nella classe:

class ClassToTest { 
    SessionIdAccessor sessionIdAccessor; 

    public ClassToTest(SessionIdAccessor sessionIdAccessor) { 
     this.sessionIdAccessor = sessionIdAccessor; 
    } 

    public void doSomething() { 
     String sessionId = sessionIdAccessor.getSessionId(); 
     // Do something with sessionId 
    } 
} 

Quindi è possibile fornire un'implementazione fittizia per i test:

public class DummySessionIdAccessor implements SessionIdAccessor { 
    public String getSessionId() { 
     return "dummy session id"; 
    } 
} 

Questo tipo di cose mette in evidenza una consueta best practice per nascondere determinati dettagli ambientali dietro alle astrazioni, in modo che tu possa scambiarli se il tuo ambiente cambia. Ciò vale anche per rendere i test meno fragili sostituendo le implementazioni fittizie con quelle "reali".

+0

Grazie, @PaulGrime. La seconda soluzione è quella che ho considerato, ma stavo cercando una soluzione in cui non dovevo cambiare il mio codice principale ... Userò questa soluzione se nessuno suggerisce un'altra soluzione migliore! – satoshi

1

Se il metodo contenente:

String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId(); 

è il metodo di controllo web, allora vi consiglio di cambiare la firma del metodo, in modo da/primavera passare la richiesta come un paremter separato per il metodo.

Quindi è possibile rimuovere la parte di troublemaker String RequestContextHolder.currentRequestAttributes() e utilizzare direttamente lo HttpSession.

Quindi nel test dovrebbe essere molto semplice utilizzare un oggetto Session fittato (MockHttpSession).

@RequestMapping... 
public ModelAndView(... HttpSession session) { 
    String id = session.getId(); 
    ... 
} 
93

Spring test ha una richiesta flessibile chiamata MockHttpServletRequest.

MockHttpServletRequest request = new MockHttpServletRequest(); 
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); 
+3

Questa dovrebbe essere la risposta accettata! – Eugene

+2

D'accordo con @Eugene. –

+0

potrebbe essere necessario aggiungere una dipendenza esplicita a javax.servlet: javax.servlet-api per la compilazione precedente (Spring 5 non lo inserisce) – djb

Problemi correlati