Тестирование проекта с использованием Guice Servlet: как связать экземпляры RequestScoped вне области действия?

У нас есть веб-приложение на Java, которое создается с использованием Guice и расширения Guice Servlet. Приложение также включает в себя задания Quartz, которые также создаются Guice. Я хочу написать модульный тест для этих заданий.

Классы заданий зависят от других классов нашего производительного кода, которые требуют Provider<HttpServletRequest>, В продуктивной настройке задания могут быть успешно созданы Guice, а задания работают как положено, потому что они никогда не вызывают какой-либо код на своих соавторах, который вызывает get на запросе сервлета провайдера. Такой get вызов завершится с ошибкой, потому что задания выполняются некоторым рабочим потоком, а не как часть HTTP-запроса к сервлету.

Теперь мой вопрос заключается в том, как настроить Guice в тесте JUnit так, чтобы я получал то же поведение, то есть классы заданий могут быть созданы, но все попытки использовать какие-либо вне области видимости @RequestScoped объекты потерпят неудачу.


Есть два простых решения, но они не работают для меня.

Если я не буду связывать HttpServletRequestЯ получаю сообщение об ошибке при попытке создать экземпляр задания в настройках теста:

com.google.inject.CreationException: Guice creation errors:

1) No implementation for javax.servlet.http.HttpServletRequest was bound.
  while locating com.google.inject.Provider<javax.servlet.http.HttpServletRequest>

Если, с другой стороны, я просто связываю фиктивные экземпляры, например, с

@Override
protected void configure() {
    bind(HttpServletRequest.class).toInstance(mock(HttpServletRequest.class));
}

тогда задание может быть создано, но я больше не получаю тестовую ошибку, если задание использует экземпляр запроса сервлета. Итак, как я могу создать привязку, чтобы Guice мог создавать экземпляры поставщиков, но любое использование поставщиков потерпит неудачу?

1 ответ

Решение

После нескольких попыток я узнал, как связать @RequestScoped объекты в тестовом модуле:

@Override
protected void configure() {
    bindScope(RequestScoped.class, ServletScopes.REQUEST);

    bind(ServletRequest.class).toProvider(unusableProvider(ServletRequest.class)).in(RequestScoped.class);
    bind(HttpServletRequest.class).toProvider(unusableProvider(HttpServletRequest.class)).in(RequestScoped.class);
    bind(ServletResponse.class).toProvider(unusableProvider(ServletResponse.class)).in(RequestScoped.class);
    bind(HttpServletResponse.class).toProvider(unusableProvider(HttpServletResponse.class)).in(RequestScoped.class);
}

private static <T> Provider<T> unusableProvider(final Class<T> type) {
    return new Provider<T>() {
        @Override
        public T get() {
            throw new IllegalStateException("Unexpected call to provider of " + type.getSimpleName());
        }
    };
}

Тесты никогда не входят в область запроса (т.е. нет вызовов ServletScope.scopeRequest), поэтому все попытки получить RequestScoped объекты в тестах приведут к точно такой же ошибке, как и в производстве:

com.google.inject.ProvisionException: Guice provision errors:

1) Error in custom provider, com.google.inject.OutOfScopeException: Cannot access scoped object. Either we are not currently inside an HTTP Servlet request, or you may have forgotten to apply com.google.inject.servlet.GuiceFilter as a servlet filter for this request.

Связывание с "непригодным провайдером" на самом деле не нужно - я просто сделал это в качестве дополнительной системы безопасности. Это означает, что это решение также должно работать для классов, которые явно не связаны, но которые помечены @RequestScoped и автоматически обнаруживается Guice.

Другие вопросы по тегам