2012-02-23 9 views
28

Piszę badanej jednostki dla metody, która zawiera następujący wiersz:Unit Testing metodę zależną od kontekstu żądanie

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

pojawia się następujący błąd:

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.

Powód jest dość oczywiste - nie używam testu w kontekście żądania.

Pytanie brzmi: jak mogę przetestować metodę, która zawiera wywołanie metody zależnej od kontekstu żądania w środowisku testowym?

Dziękuję bardzo.

Odpowiedz

30

Możesz udawać/ogłuszać obiekt RequestAttributes, aby zwrócić to, co chcesz, a następnie zadzwonić pod numer RequestContextHolder.setRequestAttributes(RequestAttributes) za pomocą swojego makiety/kodu pośredniczącego przed rozpoczęciem testu.

@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

Dziękuję @ nicholas.hauschild. Idealne i czyste rozwiązanie! – satoshi

+0

@nicholas, dzięki za szybkie rozwiązanie, zadziałało dla mnie, dzięki jeszcze raz – Bravo

1

Zakładając, że klasa jest coś takiego:

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

Jeśli nie mają zdolność do zmiany klasy, która używa RequestContextHolder, to można zastąpić klasę RequestContextHolder w kodzie testowym. tj. Tworzysz klasę o tej samej nazwie, w tym samym pakiecie i upewniasz się, że jest ona załadowana przed właściwą klasą Spring.

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

Teraz, gdy testy zakończą, będą one podnieść swoją klasę RequestContextHolder i używać zamiast do wiosny jednego (zakładając, że ścieżka klasy jest skonfigurowany tak się stało). To nie jest dobry sposób na uruchomienie testów, ale może być konieczne, jeśli nie możesz zmienić testowanej klasy.

Można również ukryć pobieranie identyfikatora sesji za abstrakcją. Na przykład wprowadzenie interfejs:

public interface SessionIdAccessor { 
    public String getSessionId(); 
} 

Utwórz realizacji:

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

i skorzystać z abstrakcji w swojej klasie:

class ClassToTest { 
    SessionIdAccessor sessionIdAccessor; 

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

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

Następnie można zapewnić obojętne wdrożenia dla swoich badań:

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

Tego typu rzeczy podkreślają najlepszą praktykę, aby ukryć pewne szczegóły środowiskowe za abstrakcjami, aby można je było wymienić, jeśli zmieni się twoje środowisko. Odnosi się to również do tego, że twoje testy są mniej kruche, zamieniając fałszywe implementacje na "prawdziwe".

+0

Dziękuję, @PaulGrime. Drugie rozwiązanie to to, które rozważałem, ale szukałem rozwiązania, w którym nie musiałbym zmieniać głównego kodu ... Użyję tego rozwiązania, jeśli nikt nie zaproponuje innego lepszego rozwiązania! – satoshi

1

Jeśli metoda zawierająca:

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

jest metoda kontroler www, to polecam zmienić podpis metody, abyście/wiosna przekazać żądanie jako paremter oddzielne do tej metody.

Następnie można usunąć uszkodzoną część String RequestContextHolder.currentRequestAttributes() i korzystać z HttpSession bezpośrednio.

Wtedy powinno być bardzo łatwo używać w teście wyśmiewanego obiektu Session (MockHttpSession).

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

Wiosenny test ma elastyczny przykład zapytania o nazwie MockHttpServletRequest.

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

To powinna być zaakceptowana odpowiedź! – Eugene

+2

Zgadzam się z @Eugene. –

+0

możesz potrzebować dodać jawną zależność do javax.servlet: javax.servlet-api dla powyższego do kompilacji (Spring 5 nie wciąga tego) – djb

Powiązane problemy