запросить тестирование бобов в весеннем тестировании - PullRequest
26 голосов
/ 09 марта 2010

Я бы хотел использовать бины с областью запроса в моем приложении. Я использую 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 как подсказывают документы (хорошо, ссылка на документацию к версии 3.8, но я использую 4.4). Так что я меняю тест для расширения AbstractJUnit4SpringContextTests ... то же сообщение. Та же проблема. А теперь я хочу метод prepareTestInstance () переопределить не определено. Хорошо, может быть, я помещу эти вызовы registerScope где-нибудь еще ... Поэтому я прочитал больше о TestExecutionListeners и думаю, что было бы лучше, так как я не хочу наследовать структуру пакета Spring. Так Я изменил свой тест на:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
@TestExecutionListeners({})
public class TestScopedBeans {

ожидал, что мне придется создать пользовательский слушатель, но я, когда запустил его. Оно работает! Отлично, но почему? Я не вижу, где любой из слушателей запаса регистрируют область запроса или сессию, и зачем они? пока нечего сказать, я хочу этого, это может быть не тест для Spring MVC кода ...

Ответы [ 8 ]

51 голосов
/ 11 января 2011

Решение для Spring 3.2 или новее

Spring, начиная с версии 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>

Исходный код

Исходный код для всех представленных решений:

8 голосов
/ 19 марта 2013

Я пробовал несколько решений, в том числе решение @ 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>

Удачи!

8 голосов
/ 10 марта 2010

Тест проходит, потому что он ничего не делает:)

Когда вы опускаете аннотацию @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, и вы можете предположить, что он работает.

4 голосов
/ 08 июня 2016

Решение, протестированное на Spring 4, для случаев, когда вам нужны bean-объекты в области запросов, но вы не делаете запросов через MockMVC и т. Д.

2 голосов
/ 25 ноября 2013

Тестирование 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, также легко реализовать Custom, как описано в статье.

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;
    }
}
2 голосов
/ 10 февраля 2011

Это все еще открытый вопрос:

https://jira.springsource.org/browse/SPR-4588

Мне удалось заставить это работать (в основном), определив пользовательский загрузчик контекста, как описано в

http://forum.springsource.org/showthread.php?p=286280

1 голос
/ 20 декабря 2012

Решение MariuszS работает, за исключением того, что мне не удалось совершить транзакцию должным образом.

Похоже, что недавно выпущенный 3.2 наконец-то сделал бины с тестовым запросом / сессией в качестве первоклассных граждан. Вот пара блогов для более подробной информации.

Россен Стоянчев Spring Framework 3.2 RC1: Spring MVC Test Framework

Сэм Браннен Spring Framework 3.2 RC1: новые возможности тестирования

0 голосов
/ 08 августа 2012

НЕ чтение документов иногда сводит с ума. Почти.

Если вы используете бины с более коротким сроком действия (например, область запроса), вам, скорее всего, также нужно изменить ленивый параметр инициализации по умолчанию! В противном случае WebAppContext не сможет загрузиться и сообщит вам что-то об отсутствующей области запроса, которая, конечно, отсутствует, поскольку контекст все еще загружается!

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-lazy-init

Ребята из Spring определенно должны указать эту подсказку в своем сообщении об исключении ...

Если вы не хотите изменять значение по умолчанию, есть также способ аннотации: поместите «@Lazy (true)» после @Component и т. Д., Чтобы синглеты инициализировались лениво и избегали создания экземпляров bean-объектов, определяемых запросами, слишком рано. 1010 *

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...