web-dev-qa-db-fra.com

demander des haricots scoped au printemps

Je voudrais faire usage de demande haricots hachés dans mon application. J'utilise JUnit4 pour les tests. Si j'essaie d'en créer un dans un test comme celui-ci:

@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);
    }

Avec la définition de bean suivante:

 <?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>           

Et je reçois:

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'

J'ai donc trouvé ce blog qui semblait utile: http://www.javathinking.com/2009/06/no-scope-registered-for-scope-request_5.html

Mais j'ai remarqué qu'il utilise AbstractDependencyInjectionSpringContextTests qui semble être obsolète au printemps 3.0. J'utilise Spring 2.5 pour le moment, mais je pense qu'il ne devrait pas être trop difficile de changer cette méthode pour utiliser AbstractJUnit4SpringContextTests Comme le suggère la documentation (ok, le lien de la documentation à la version 3.8 mais j'utilise la version 4.4) . Je modifie donc le test .__ pour étendre AbstractJUnit4SpringContextTests ... même message. Même problème. Et maintenant, la méthode prepareTestInstance () que je veux remplacer par __ n'est pas définie. OK, peut-être que je mettrai ces appels registerScope ailleurs ... Je lis donc plus à propos de TestExecutionListeners et pense que ce serait mieux, car je ne souhaite pas hériter de la structure de package Spring. Alors J'ai changé mon test en:

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

en espérant que je devrais créer un auditeur personnalisé, mais je l’ai quand je l’ai exécuté. Ça marche! Génial, mais pourquoi? Je ne vois pas où les auditeurs d'actions Sont en train d'enregistrer l'étendue de la demande ou de la session, et pourquoi le feraient-ils? il n'y a rien à dire, je le veux encore, cela pourrait ne pas être un test pour le code Spring MVC ...

26
harschware

Le test réussit car il ne fait rien :)

Lorsque vous omettez l'annotation @TestExecutionListeners, Spring enregistre 3 écouteurs par défaut, dont l'un appelé DependencyInjectionTestExecutionListener. C'est l'auditeur responsable de l'analyse de votre classe de test à la recherche d'éléments à injecter, y compris les annotations @Resource. Cet écouteur a essayé d'injecter tObj et a échoué en raison de l'étendue indéfinie.

Lorsque vous déclarez @TestExecutionListeners({}), vous supprimez l'enregistrement de DependencyInjectionTestExecutionListener et le test ne reçoit jamais tObj injecté. Et comme votre test ne vérifie pas l'existence de tObj, il réussit.

Modifiez votre test pour qu'il le fasse et qu'il échouera:

@Test
public void testBean() {
    assertNotNull("tObj is null", tObj);
}

Donc, avec votre @TestExecutionListeners vide, le test réussit car rien ne se produit.

Passons maintenant à votre problème initial. Si vous souhaitez essayer d'enregistrer l'étendue de la demande dans votre contexte de test, jetez un coup d'œil au code source de WebApplicationContextUtils.registerWebApplicationScopes(). Vous y trouverez la ligne:

beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());

Vous pouvez essayer cela et voir comment vous allez, mais il peut y avoir des effets secondaires étranges, car vous n'êtes pas vraiment censé le faire lors d'un test.

Au lieu de cela, je vous recommanderais de reformuler votre test afin que vous n’ayez pas besoin de demander des beans scoped. Cela ne devrait pas être difficile, le cycle de vie du @Test ne devrait pas être plus long que celui d'un bean à la demande, si vous écrivez des tests autonomes. N'oubliez pas qu'il n'est pas nécessaire de tester le mécanisme de définition de la portée, cela fait partie de Spring et vous pouvez en déduire que cela fonctionne. 

8
skaffman

Solution pour Spring 3.2 ou plus récent

Spring à partir de la version 3.2 fournit un support pour les beans sectorisés session/request pour les tests d'intégration .

@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));
    }
}

En savoir plus: Requête et session aux haricots


Solution pour Spring avant 3.2 avec auditeur

@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());
        }
    }
}

Solution pour Spring avant 3.2 avec des étendues personnalisées

@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

}

ou avec configuration 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>

Code source

Code source pour toutes les solutions présentées:

50
MariuszS

J'ai essayé plusieurs solutions, y compris la solution de @ Marius avec "WebContextTestExecutionListener", mais cela n'a pas fonctionné pour moi car ce code a chargé le contexte de l'application avant de créer l'étendue de la demande.

La réponse qui m'a aidé à la fin n'est pas nouvelle, mais c'est bien: http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope -des haricots/

J'ai simplement ajouté l'extrait suivant à mon contexte d'application (test):

<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>

Bonne chance!

8
Ido Cohn

Une solution, testée avec Spring 4, pour les cas où vous avez besoin de haricots à demande, mais ne faites aucune demande via MockMVC, etc.

@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()));
    }

    // ...
4
OrangeDog

Testez les haricots à demande avec Spring explique très bien comment enregistrer et créer une étendue personnalisée avec Spring. 

En résumé, comme expliqué par Ido Cohn, il suffit d'ajouter ce qui suit à la configuration du contexte de texte:

<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>

Au lieu d'utiliser le SimpleThreadScope prédéfini, basé sur ThreadLocal, il est également facile d'implémenter un Custom, comme expliqué dans l'article.

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
stivlo

C'est toujours un problème en suspens:

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

J'ai pu obtenir que cela fonctionne (principalement) en définissant un chargeur de contexte personnalisé, comme indiqué dans 

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

2
Jim Cox

La solution de MariuszS fonctionne, sauf que je n'ai pas pu valider la transaction correctement.

Il semble que la version 3.2 récemment publiée ait finalement mis à l’essai des citoyens de première classe axés sur les demandes et les sessions de test. Voici quelques blogs pour plus de détails.

Rossen Stoyanchev's Spring Framework 3.2 RC1: Cadre de test MVC de printemps

Sam Brannen's Spring Framework 3.2 RC1: Nouvelles fonctionnalités de test

1
Christopher Yang

Ne pas lire la documentation rend parfois fou. Presque.

Si vous utilisez des haricots à durée de vie plus courte (portée de la demande par exemple), vous devrez probablement aussi changer votre valeur par défaut init paresseux! Sinon, WebAppContext ne parviendra pas à se charger et vous dira quelque chose à propos de la portée de la requête manquante, qui est bien entendu manquante, car le contexte continue de se charger!

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

Les gars du printemps devraient absolument mettre cet indice dans leur message d'exception ...

Si vous ne souhaitez pas modifier la valeur par défaut, il existe également un moyen d'annotation: insérez "@Lazy (true)" après @Component, etc. pour que les singletons s'initialisent paresseux et évitent d'instancier trop tôt des beans de requête.

0
user1050755