Подтвердить что ты не робот

Единичное тестирование метода, зависящего от контекста запроса

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

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

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

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

Причина совершенно очевидна - я не запускаю тест в контексте запроса.

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

@Mock
private RequestAttributes attrs;

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

    // do you when on attrs
}

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

Ответ 3

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

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 один (при условии, что для этого будет создан путь к классам). Это не очень хороший способ запуска ваших тестов, но может потребоваться, если вы не можете изменить тестируемый класс.

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

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

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

Ответ 4

Если метод содержит:

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

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

Затем вы можете удалить часть нарушителя String RequestContextHolder.currentRequestAttributes() и использовать HttpSession прямо.

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

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

Ответ 5

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

@Mock
HttpServletRequest request;

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
}