запросить bean-компоненты с областью действия при весеннем тестировании
-
18-09-2019 - |
Вопрос
Я хотел бы использовать bean-компоненты с областью запроса в своем приложении.Я использую JUnit4 для тестирования.Если я попытаюсь создать его в таком тесте:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
public class TestScopedBeans {
protected final static Logger logger = Logger
.getLogger(TestScopedBeans.class);
@Resource
private Object tObj;
@Test
public void testBean() {
logger.debug(tObj);
}
@Test
public void testBean2() {
logger.debug(tObj);
}
Со следующим определением компонента:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="java.lang.Object" id="tObj" scope="request" />
</beans>
И я получаю:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'gov.nasa.arc.cx.sor.query.TestScopedBeans': Injection of resource fields failed; nested exception is java.lang.IllegalStateException: No Scope registered for scope 'request'
<...SNIP...>
Caused by: java.lang.IllegalStateException: No Scope registered for scope 'request'
Итак, я нашел этот блог, который показался мне полезным:http://www.javathinking.com/2009/06/no-scope-registered-for-scope-request_5.html
Но я заметил, что он использует AbstractDependencyInjectionSpringContextTests который, похоже, устарел в Spring 3.0.Я использую Spring 2.5 в настоящее время, но думал, что не должно быть слишком сложно переключать этот метод, чтобы использовать AbstractJunit4SpringContextTests, как предлагают документы (OK, ссылка DOCS на версию 3.8, но я использую 4.4).Поэтому я меняю тест, чтобы расширить AbstractJunit4springContextTests ...то же сообщение.Та же проблема.И теперь метод PreparetestInstance (), который я хочу переопределить, не определяется.Хорошо, может быть, я помещу вызовы RegisterScope куда-нибудь еще...Поэтому я читаю больше о Слушатели TestExecution и думаю, что это было бы лучше, поскольку я не хочу наследовать структуру пакета Spring.Итак, я изменил свой тест на:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
@TestExecutionListeners({})
public class TestScopedBeans {
ожидал, что мне придется создать собственный прослушиватель, но я запустил его.Оно работает!Отлично, но почему?Я не вижу, где кто -то из акционерных слушателей регистрирует объем запросов или сессию сессии, и зачем им?пока нечего сказать, я хочу этого, возможно, это не тест на код Spring MVC...
Решение
Тест пройден, потому что он ничего не делает :)
Когда вы опускаете @TestExecutionListeners
аннотации, Spring регистрирует 3 прослушивателя по умолчанию, включая один, вызываемый DependencyInjectionTestExecutionListener
.Это прослушиватель, ответственный за сканирование вашего тестового класса в поисках элементов для внедрения, в том числе @Resource
аннотации.Этот слушатель пытался внедрить tObj
, и терпит неудачу из-за неопределенной области действия.
Когда вы заявляете @TestExecutionListeners({})
, вы подавляете регистрацию DependencyInjectionTestExecutionListener
, и поэтому тест никогда не проходит tObj
вообще не вводится, и поскольку ваш тест не проверяет существование tObj
, оно проходит.
Измените свой тест так, чтобы он выполнял это, и он завершится неудачно:
@Test
public void testBean() {
assertNotNull("tObj is null", tObj);
}
Итак, с вашим пустым @TestExecutionListeners
, тест пройден, потому что Ничего не произошло.
Теперь перейдем к вашей исходной проблеме.Если вы хотите попробовать зарегистрировать область запроса в тестовом контексте, посмотрите исходный код для WebApplicationContextUtils.registerWebApplicationScopes()
, вы найдете строку:
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
Вы могли бы попробовать это и посмотреть, как пойдет дело, но могут возникнуть странные побочные эффекты, потому что на самом деле вы не предназначены для этого в тесте.
Вместо этого я бы рекомендовал перефразировать ваш тест, чтобы вы не нуждаться запросить bean-компоненты с ограниченной областью действия.Это не должно быть сложно, жизненный цикл @Test
не должен превышать жизненный цикл bean-компонента с областью запроса, если вы пишете автономные тесты.Помните, что нет необходимости тестировать механизм области видимости, он является частью Spring, и вы можете предположить, что он работает.
Другие советы
Решение для Spring 3.2 или новее
Весна, начиная с версии 3.2 обеспечивает поддержку bean-компонентов с областью действия сеанса/запроса для интеграционного тестирования.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@WebAppConfiguration
public class SampleTest {
@Autowired WebApplicationContext wac;
@Autowired MockHttpServletRequest request;
@Autowired MockHttpSession session;
@Autowired MySessionBean mySessionBean;
@Autowired MyRequestBean myRequestBean;
@Test
public void requestScope() throws Exception {
assertThat(myRequestBean)
.isSameAs(request.getAttribute("myRequestBean"));
assertThat(myRequestBean)
.isSameAs(wac.getBean("myRequestBean", MyRequestBean.class));
}
@Test
public void sessionScope() throws Exception {
assertThat(mySessionBean)
.isSameAs(session.getAttribute("mySessionBean"));
assertThat(mySessionBean)
.isSameAs(wac.getBean("mySessionBean", MySessionBean.class));
}
}
Читать далее: Компоненты запроса и сеанса
Решение для Spring до версии 3.2 с прослушивателем
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@TestExecutionListeners({WebContextTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class})
public class SampleTest {
...
}
WebContextTestExecutionListener.java
public class WebContextTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) {
if (testContext.getApplicationContext() instanceof GenericApplicationContext) {
GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext();
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST,
new SimpleThreadScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION,
new SimpleThreadScope());
}
}
}
Решение для Spring до версии 3.2 с настраиваемыми областями действия
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class, locations = "test-config.xml")
public class SampleTest {
...
}
TestConfig.java
@Configuration
@ComponentScan(...)
public class TestConfig {
@Bean
public CustomScopeConfigurer customScopeConfigurer(){
CustomScopeConfigurer scopeConfigurer = new CustomScopeConfigurer();
HashMap<String, Object> scopes = new HashMap<String, Object>();
scopes.put(WebApplicationContext.SCOPE_REQUEST,
new SimpleThreadScope());
scopes.put(WebApplicationContext.SCOPE_SESSION,
new SimpleThreadScope());
scopeConfigurer.setScopes(scopes);
return scopeConfigurer
}
или с конфигурацией XML
test-config.xml
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
<map>
<entry key="session">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
Исходный код
Исходный код всех представленных решений:
Я пробовал несколько решений, в том числе решение @Marius с «WebContextTestExecutionListener», но у меня оно не сработало, так как этот код загружал контекст приложения перед созданием области запроса.
Ответ, который мне в итоге помог, не новый, но хороший:http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope-beans/
Я просто добавил следующий фрагмент в контекст моего (тестового) приложения:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
Удачи!
Решение, протестированное с помощью Spring 4, на случай, если вам требуются bean-компоненты с областью запроса, но вы не отправляете никаких запросов через MockMVC
, и т. д.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(/* ... */)
public class Tests {
@Autowired
private GenericApplicationContext context;
@Before
public void defineRequestScope() {
context.getBeanFactory().registerScope(
WebApplicationContext.SCOPE_REQUEST, new RequestScope());
RequestContextHolder.setRequestAttributes(
new ServletRequestAttributes(new MockHttpServletRequest()));
}
// ...
Это все еще открытый вопрос:
https://jira.springsource.org/browse/SPR-4588
Мне удалось заставить это работать (в основном), определив собственный загрузчик контекста, как описано в
Тестирование bean-компонентов с областью запроса с помощью Spring очень хорошо объясняет, как зарегистрироваться и создать собственную область видимости с помощью Spring.
В двух словах, как объяснил Идо Кон, достаточно добавить в конфигурацию текстового контекста следующее:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
Вместо использования предопределенного SimpleThreadScope, основанного на ThreadLocal, также легко реализовать собственный, как описано в статье.
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class CustomScope implements Scope {
private final Map<String , Object> beanMap = new HashMap<String , Object>();
public Object get(String name, ObjectFactory<?> factory) {
Object bean = beanMap.get(name);
if (null == bean) {
bean = factory.getObject();
beanMap.put(name, bean);
}
return bean;
}
public String getConversationId() {
// not needed
return null;
}
public void registerDestructionCallback(String arg0, Runnable arg1) {
// not needed
}
public Object remove(String obj) {
return beanMap.remove(obj);
}
public Object resolveContextualObject(String arg0) {
// not needed
return null;
}
}
Решение MariuszS работает, но мне не удалось правильно зафиксировать транзакцию.
Кажется, что недавно выпущенная версия 3.2 наконец-то сделала тестирование bean-компонентов с областью запроса/сеанса первоклассным.Вот пара блогов для более подробной информации.
Россена Стоянчева Spring Framework 3.2 RC1:Spring Тестовая среда MVC
Сэм Браннен Spring Framework 3.2 RC1:Новые возможности тестирования
НЕЧТЕНИЕ документации иногда сводит с ума.Почти.
Если вы используете bean-компоненты с более коротким сроком жизни (например, область запроса), вам, скорее всего, также потребуется изменить значение по умолчанию для ленивой инициализации!В противном случае WebAppContext не сможет загрузиться и сообщит вам что-то об отсутствующей области запроса, которая, конечно же, отсутствует, поскольку контекст все еще загружается!
Ребятам из Spring обязательно стоит добавить этот намек в свое сообщение об исключении...
Если вы не хотите менять значение по умолчанию, есть также способ аннотации:поместите «@Lazy(true)» после @Component и т. д.чтобы сделать синглтоны ленивыми при инициализации и избежать слишком раннего создания экземпляров bean-компонентов в области запроса.