Модульное тестирование метода в зависимости от контекста запроса - PullRequest
45 голосов
/ 23 февраля 2012

Я пишу модульный тест для метода, который содержит следующую строку:

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

Я получаю следующую ошибку:

java.lang.IllegalStateException: НетОбнаружен запрос с привязкой к потоку: Вы ссылаетесь на атрибуты запроса вне фактического веб-запроса или обрабатываете запрос вне первоначально полученного потока?Если вы действительно работаете с веб-запросом и по-прежнему получаете это сообщение, ваш код, вероятно, выполняется за пределами DispatcherServlet / DispatcherPortlet: в этом случае используйте RequestContextListener или RequestContextFilter для предоставления текущего запроса.причина вполне очевидна - я не запускаю тест в контексте запроса.

Вопрос в том, как протестировать метод, содержащий вызов метода, зависящего от контекста запроса, в тестовой среде?

Большое спасибо.

Ответы [ 5 ]

145 голосов
/ 24 февраля 2012

Spring-test имеет гибкий макет запроса под названием MockHttpServletRequest.

MockHttpServletRequest request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
40 голосов
/ 23 февраля 2012

Вы можете смоделировать / заглушить объект RequestAttributes, чтобы вернуть то, что вы хотите, и затем вызвать RequestContextHolder.setRequestAttributes(RequestAttributes) с вашим макетом / заглушкой перед началом теста.

@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 голос
/ 24 февраля 2012

Если метод, содержащий:

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

, является методом веб-контроллера, то я бы порекомендовал изменить сигнатуру метода, чтобы вы / весной передавали запрос в качестве отдельного паремтера для метода.

Затем вы можете удалить возбуждающую часть String RequestContextHolder.currentRequestAttributes() и использовать HttpSession.

Тогда использовать тестовый объект Session (MockHttpSession) в тесте должно быть очень просто..

@RequestMapping...
public ModelAndView(... HttpSession session) {
    String id = session.getId();
    ...
}
1 голос
/ 23 февраля 2012

Предполагая, что ваш класс что-то вроде:

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

Если у вас нет возможности изменить класс, который использует RequestContextHolder, тогда вы можете переопределить класс RequestContextHolder в своем тестовом коде. То есть вы создаете класс с тем же именем в том же пакете и гарантируете, что он загружен до фактического класса 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.
    }
}

Теперь, когда ваши тесты будут запущены, они подберут ваш класс RequestContextHolder и будут использовать его по сравнению с классом Spring (при условии, что для этого настроен classpath). Это не очень хороший способ запустить ваши тесты, но он может быть необходим, если вы не можете изменить класс, который вы тестируете.

Кроме того, вы можете скрыть получение идентификатора сеанса за абстракцией. Например, введите интерфейс:

public interface SessionIdAccessor {
    public String getSessionId();
}

Создать реализацию:

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

И используйте абстракцию в своем классе:

class ClassToTest {
    SessionIdAccessor sessionIdAccessor;

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

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

Затем вы можете предоставить фиктивную реализацию для ваших тестов:

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

Подобные вещи выдвигают на первый план обычную лучшую практику, позволяющую скрывать некоторые детали среды за абстракциями, чтобы вы могли поменять их, если ваша среда изменится. Это в равной степени относится и к тому, чтобы сделать ваши тесты менее хрупкими путем замены фиктивных реализаций на «настоящие».

0 голосов
/ 11 апреля 2019

Я смог сделать то же самое, что и этот ответ , но без класса MockHttpServletRequest, используя аннотацию @Mock.Я думаю, они похожи.Просто публикуем здесь для будущих посетителей.

@Mock
HttpServletRequest request;

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...