Spring Test's MockMvc and Spring Remoting's HttpInvoker - java

I've got web application which is split into two parts
(being run in different jvms):
#RestController layer;
#Service layer (business and data access logic).
They communicate with each other via Spring Remoting:
(org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean
on #RestController layer and
org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
on #Service layer).
These two parts are deployed on different application servers.
Mostly they are tested via Spring RestTemplate
(#Service part has to be deployed and started manually
and after that integration tests are run).
But as I used Spring Test and MockMvc in past and found it to be a great tool
I would like to use again and again.
Unfortunatelly I do not understand how can I add #Service layer context into test context configuration such way,
that it would be accessible from test (which holds #RestController context augmented with some mocks).
If I manually start application server with #Service layer artifacts (on localhost) and run mine MockMvc-driven test I can see that remote requests
from MockMvc get to their destination - #Service layer
(through httpInvoker of course).
And I want to find possibility to start #Service layer context within test context (with all needed HttpInvokerServiceExporters).
And to force httpInvoker to send its requests to this "pseudo" remote service (which in fact will be local).
Now I'm thinking about using embedded jetty for deploying #Service layer
and running MockMvc tests against this instance.
SpringHttpRemoting With EmbeddedJettyServer.wiki
I have very small experience in MicroService architecture
but it seems that mine situation is rather usual for it.
So maybe there are some more natural
(in spite of Spring Test and MockMvc particularly) ways for such testing ?
Thanks in advance.
Andrey.

Well, let me share my thoughts upon the matter.
Here is domain and api:
public class Contact implements Serializable {
private String firstName;
private String lastName;
private DateTime birthDate;
}
public interface ContactService {
List<String> getContacts();
}
#Service
public class ContactServiceImpl implements ContactService{
public List<String> getContacts() {
return new ArrayList<String>(asList("karl marx", " fridrih engels", " !!!"));
}
}
#RestController
public class ContactController {
public static final String QUALIFIER = "contactController";
public static final String MAPPING = "/contact";
#Autowired
private ContactService serviceInvoker;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<String> findAll() {
List<String> strings = serviceInvoker.getContacts();
return new ResponseEntity<String>(Arrays.toString(strings.toArray()), HttpStatus.OK);
}
}
It is rather simple to test such configuration by providing test with 'invoking side' context (restcontroller in my case)
and 'invoked side' context (containing real services, not remote proxies). Just as with monolith application context.
Fast and easy approach. But in some cases not enough
(e.g. You have customized HttpInvokerProxyFactoryBean on one side and customized HttpInvokerServiceExporter on the other for some purposes).
You can override HttpInvokerProxyFactoryBean class making it NonRemote.
First, modify HttpInvokerServiceExporter by overriding some of its methods; it is just needed to make methods connected to RemoteInvocation and RemoteInvocationResult public.
public class OpenedHttpServiceExporter extends HttpInvokerServiceExporter {
#Override
public RemoteInvocation readRemoteInvocation(HttpServletRequest request) throws IOException, ClassNotFoundException {
return super.readRemoteInvocation(request);
}
.
.
.
etc...
}
Let it be OpenedHttpServiceExporter. Create bean descriptor in test/resources, import production beans definitions which You need in test into it
and add OpenedHttpServiceExporter bean with the same name as original HttpInvokerServiceExporter has - it is needed for overriding one with the other.
test context descriptor openedServiceExporter.xml (without beans element):
<import resource="classpath:spring/serviceExporter.xml"/>
<bean id="contactExporter" class="pmp.testingremoting.service.OpenedHttpServiceExporter">
<property name="service" ref="contactServiceImpl"/>
<property name="serviceInterface" value="pmp.testingremoting.service.ContactService"/>
</bean>
And imported descriptor:
<context:annotation-config/>
<context:component-scan base-package="pmp.testingremoting.service">
<context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
</context:component-scan>
<bean name="contactExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="contactServiceImpl"/>
<property name="serviceInterface" value="pmp.testingremoting.service.ContactService"/>
</bean>
Extend HttpInvokerProxyFactoryBean, make a bean of this subclass, autowire HttpInvokerServiceExporter field into it.
Override
public Object invoke(MethodInvocation methodInvocation)
by calling OpenedHttpServiceExporter.invoke(createRemoteInvocation(methodInvocation), exporter.getService());
in it.
public class NonRemoteInvoker extends HttpInvokerProxyFactoryBean {
#Autowired
private OpenedHttpServiceExporter exporter;
public void setExporter(OpenedHttpServiceExporter exporter) {
this.exporter = exporter;
}
#Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
return exporter.invoke(createRemoteInvocation(methodInvocation), exporter.getService());
}
}
Let's call this new class NonRemoteInvoker. It has to override only one method of super class and will serve as a bridge from 'invoking side' context to 'invoked side' context.
Create 'invoking side' test context descriptor (nonRemoteInvokerContext.xml) with an instance of NonRemoteInvoker
(again, with same name as original HttpInvokerProxyFactoryBean has; also for overriding).
<import resource="classpath:spring/webContext.xml"/>
<bean id="serviceInvoker" class="pmp.testingremoting.controller.NonRemoteInvoker">
<property name="serviceUrl" value="http://localhost:8080/remote/ContactService" />
<property name="serviceInterface" value="pmp.testingremoting.service.ContactService" />
</bean>
and webContext.xml is
<mvc:annotation-driven/>
<context:annotation-config/>
<context:component-scan base-package="pmp.testingremoting.controller">
<context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
</context:component-scan>
Create test configuration for 'invoked side'. I used static #Configuration class and just imported test context descriptor into it.
#Configuration
#ImportResource(locations = {
"classpath:openedServiceExporter.xml"
})
static class TunedBusinessConfig {
}
Create test configuration for 'invoking side'. I did it the same way.
#Configuration
#ImportResource(locations = {
"classpath:nonRemoteInvokerContext.xml",
})
static class TunedRemoteInvokerConfig {
}
Now the test class. It will be marked via #WebAppConfiguration and have such #ContextHierarchy, that will allow 'invoking side' context to use 'invoked side' context (invoked - parent, invoking - child).
It is needed for injecting OpenedHttpServiceExporter into NonRemoteInvoker.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy({
#ContextConfiguration(classes = {
ContactControllerIntegrationTest.TunedBusinessConfig.class
}),
#ContextConfiguration(classes = {
ContactControllerIntegrationTest.TunedRemoteInvokerConfig.class
})
})
public class ContactControllerIntegrationTest {
.
.
.
}
This approach allowed me to cover in test not only rest and service layers logic, but also logic of customized RemoteInvoker (let's call it transport logic).
Here are more details:
https://github.com/PmPozitron/TestingRemoting/tree/lightVersion
I am not sure whether such approach is correct, so will not mark answer as accepted until appropriate moment.

Related

Spring Prototype-Bean Provider without #Autowired

I have a prototype Bean which is instantiated by singleton bean with a Provider:
#Component
#Scope("prototype")
class MyPrototype {}
#Component
class MySingleton {
#Autowired
javax.inject.Provider<MyPrototype> prototypeFactory;
}
This works fine, but our company rules state that #Autowired is not allowed; the common pattern is #Resource(SingletonBeanClass.BEAN_ID).
Is it possible to annotate the Provider this way so the Spring lookup can create it?
I'm aware I can add a factory method with #Lookup, or a singleton factory bean, but I prefer the Provider.
EDIT:
I didn't get it to work this way and in the end had to edit spring.xml; see below for details.
As you have an XML configuration file, you can configure it via XML in the following way:
<bean id="myPrototype" class="some.package.MyPrototype" scope="prototype" />
<bean id="mySingleton" class="some.package.MySingleton">
<lookup-method name="getPrototypeFactory" bean="myPrototype "/>
</bean>
In this way, you have to access to the myPrototype with the getPrototypeFactory() and not directly to the property. You can even remove the annotations on those 2 classes.
For any extra details, you can look at the following blog post Injecting a prototype bean into a singleton bean
For reference, if someone comes across this via Google:
I ended up needing to declare it in the spring.xml. I tried #Lookup, but even that didn't work due to the prototype-bean referencing yet another prototype-bean.
This is how it was recommended here,
but it does not work:
#Component("proto1")
#Scope("prototype")
class MyPrototypeBean1 {
#Lookup(value="proto2")
protected MyPrototypeBean2 createBean2() { return null; }
}
#Component("proto2")
#Scope("prototype")
class MyPrototypeBean2 {
}
#Component("singleton")
class MySingleton {
#Lookup(value="proto1")
protected MyPrototypeBean1 createBean1() { return null; }
}
This results in the error message "Cannot apply #Lookup to beans without corresponding bean definition" when trying to create "innerBean...".
I assume it is due to "lookup methods cannot get replaced on beans returned from factory methods where we can't dynamically provide a subclass for them" as is quoted in the link above.
So what I ended up doing in the spring.xml:
<bean name="proto2" class="my.package.PrototypeBean2" />
<bean name="proto1" class="my.package.PrototypeBean1" >
<lookup-method name="createBean2" bean="proto2" />
</bean>
<bean name="singleton" class="my.package.SingletonBean" >
<lookup-method name="createBean1" bean="proto1" />
</bean>
And this works.
For the unit tests, I had to subclass the respective classes:
class SingletonUnitTest {
#Mock
MyPrototypeBean1 bean1;
#InjectMocks
DummySingleton sut;
#Before public void setBean1() {
sut.bean = bean1;
}
static class DummySingletonBean extends MySingeton {
MyPrototypeBean1 bean;
protected MyPrototypeBean1 createBean1() {
return bean;
}
}
}

Spring unit test objects autowired with null fields

I'm attempting to create unit tests for a rest service in my spring servlet. But when the controller object is created by #autowire, then all of its #autowired fields are null.
My test class is as follows, using the SpringJUnit runner and context configuration set
#ContextConfiguration(locations = "ExampleRestControllerTest-context.xml")
#RunWith(SpringJUnit4ClassRunner.class)
public class ExampleRestControllerTest {
#Autowired
private BaseService mockExampleService;
#Autowired
private ExampleRestController testExampleRestController;
The ExampleRestControllerTest-context.xml sets up the service to be mocked and injects the mocked object into the controller
<context:annotation-config/>
<import resource="classpath*:example-servlet.xml"/>
<bean id="mockExampleService" class="org.easymock.EasyMock" factory-method="createMock">
<constructor-arg index="0" value="za.co.myexample.example.services.BaseService"/>
</bean>
<bean id="testExampleRestController" class="za.co.myexample.example.rest.controller.ExampleRestController">
<property name="exampleService" ref="mockExampleService"/>
</bean>
The rest of the beans used by the controler are defined in the example-servlet.xml
<bean id="RESTCodeMapper" class="za.co.myexample.example.rest.util.RESTCodeMapper"/>
<bean id="restProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:RESTServiceCodeMapping.properties"/>
</bean>
Along with my Jax2BMarshaller.
My IDE links to these definitions just fine and if I remove any of the definitions I get an "No qualifying bean" error as expected.
My problem is that when I run my unit tests the controller that is provided has all of its fields as nulls
#Controller
public abstract class BaseRestController {
private static Logger LOGGER = Logger.getLogger(BaseRestController.class);
protected final String HEADERS = "Content-Type=application/json,application/xml";
#Autowired
protected RESTCodeMapper restCodeMapper;
#Autowired
protected BaseService exampleService;
#Autowired
protected Jaxb2Marshaller jaxb2Marshaller;
(and its implementing class)
#Controller
#RequestMapping("/example")
public class ExampleRestController extends BaseRestController {
When I run the proper code in my Weblogic server the fields get properly populated. More so if I add these fields in my test class directly those fields also get #autowired correctly. I could thus #autowire those objects in my test class and then set them directly into my controller. But that's not the right way of doing it.
So the question is, why are the autowired fields of an autowired object null in my test class when it autowires those objects directly in the same test class or if I run the code normally on my Weblogic server.
In your test class, the #Autowired objects are null because of context configuration, so change it to:
#ContextConfiguration(locations = "ExampleRestControllerTest-context.xml")
to
#ContextConfiguration(locations = "classpath:/ExampleRestControllerTest-context.xml")
in ExampleRestControllerTest
So the question is, why are the autowired fields of an autowired
object null in my test class when it autowires those objects directly
in the same test class or if I run the code normally on my Weblogic
server.
They must be null in autowired objects' constructor. You can try to create #PostConstruct method in autowired object and in this method the autowired objects must be not null

Spring Bean Injection Failing Due To Proxy

Spring Version: 3.2.4.RELEASE and 3.2.9.RELEASE
Mockito Version: 1.8.5
I've been trying to introduce H2 tests to an old project for integration testing, and I've been running into a few issues. Due to the way transactions were propagating, I needed to mock out an autowired class. I've done this before, but I'm now running into severe problems. The following error message is being thrown when initialising the test:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.stuff.XMLITCase': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'TheProcessor' must be of type [com.stuff.XMLBatchFileProcessor], but was actually of type [$Proxy118]
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:307)
Diving into this a bit deeper, it turns out the the bean is in-fact a proxy. If we check the AbstractBeanFactory (round line 239), we can see the proxy:
sharedInstance = {$Proxy117#7035} "com.stuff.XMLBatchFileProcessor#66c540d0"
h = {org.springframework.aop.framework.JdkDynamicAopProxy#7039}
The only problem is, I've no clue where this is coming from. I've gone over the config and dependencies, and can't find anywhere that this should be happening.
Project Setup
Unfortunately I can't give a sample project for this, but I'll go over my test configuration. I have a root class that I extend for the tests:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:/spring/spring-test-context.xml"})
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public abstract class AbstractIntegrationTest {
}
This simply loads in some spring config and rolls back the transactions after each test.
The spring config is nothing strange either, though there is one difference between my other module and this one. This is the transaction manager and session factory:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="hibernateSessionFactory"/>
</bean>
<bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
...
</bean>
In my other module, I'm using an entityManagerFactory, and a different transaction manager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
The actually class has some autowired fields, and the usual #Service annotation:
#Service(value = "TheProcessor")
public final class XMLBatchFileProcessor extends BatchFileProcessor implements IXMLBatchProcessor {
Finally, the actual test is as follows:
public class XMLITCase extends AbstractIntegrationTest {
#Resource(name = "TheProcessor")
#InjectMocks
private XMLBatchFileProcessor xmlProcessor;
#Mock
private ProcessHelper processHelper;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void test() throws Exception {
Assert.assertNotNull(xmlProcessor);
}
}
If I replace the XMLBatchFileProcessor with the interface and autowire the field, then there aren't any problems compiling. Mockito, however, never replaces the autowired bean with the mocked object. If it did, then I wouldn't bother with the #Resource annotations and naming the service, thus avoiding the Proxy issue.
Any assistance on this would be appreciate. I'll be focusing on the session factory and the differences there, but it's quite possible that I'm missing something else entirely.
EDIT
Going on Sotirios' comment, I had another look this morning and indeed had missed that the xmlProcessor has a #Transactional annotation, thus meaning that the class needs to be proxied. If I remove the final declaration and let CGLib enhance it, then Mockito does replace the bean when initMocks(this) this called. When a method is called, however, CGLib seems to replace all the beans with Spring enhanced versions, hence overwriting the Mockito version.
What is the correct way to use both Mockito and Spring in an integration test for a class with #Transactional annotations?
Alright, once I realised that the class was being proxied due to the #Transactional annotation, the solution to the problem became clearer. What I needed to do was unwrap the proxy, and set the mocked object directly on that:
So in my AbstractIntegrationTest:
/**
* Checks if the given object is a proxy, and unwraps it if it is.
*
* #param bean The object to check
* #return The unwrapped object that was proxied, else the object
* #throws Exception
*/
public final Object unwrapProxy(Object bean) throws Exception {
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
Advised advised = (Advised) bean;
bean = advised.getTargetSource().getTarget();
}
return bean;
}
Then in my #Before:
#Mock
private ProcessHelper processHelper;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
IXMLBatchProcessor iXMLBatchProcessor = (IXMLBatchProcessor) unwrapProxy(xmlProcessor);
ReflectionTestUtils.setField(iXMLBatchProcessor , "processHelper", processHelper);
}
This left all the #Autowired classes intact, while injecting the correct mocked object.
you can optimise the accepted response using the class AopTestUtils that provides the methods:
getTargetObject to unwrap the top-level proxy if exists
getUltimateTargetObject to unwrap all levels of proxies if they
exist

Support for autowiring in a class not instantiated by spring (3)

I realize this should be really basic but I haven't found a second step example after Helloworld
So what I have is:
spring config xml called spring-beans.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="org" />
</beans>
A spring context initialized class:
public static void main(String[] args) {
// initialize Spring
ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring-beans.xml");
App app = (App) context.getBean("app");
app.run();
}
Relevant details of AppImpl class:
#Component("app")
public final class AppImpl implements App{
// focus of question: This autowiring works
#Autowired
private DAO1 dao1;
public void run() {
//focus of question: This works as daoclass is instantiated properly
obj1s = dao1.myFind();
badJobs = runJobs(obj1s);
}
private List<Obj1> runJobs(final List<Obj1> obj1s) {
List<Obj1> jobsGoneBad = new ArrayList<Obj1>();
for (Obj1 next : obj1s) {
// focus of question: usage of new keyword, thus not spring container managed?
Job job = new JobImpl(next);
job.run();
}
return jobsGoneBad;
}
}
Relevant details of JobImpl:
public class JobImpl implements Job {
private Obj1 obj1;
// focus of question: can't autowire
#Autowired
private DAO2 dao2;
#Override
public void run() {
//focus of question: opDAO == null - not initialized by #Autowired
Obj2 obj2 = dao2.myFind();
}
}
Relevant details of DAO1:
#Repository("DAO1") //Focus of question: DAO1 is a repository stereotype
public class DAO1 {
myfind() { ...}
}
Relevant details of DAO2:
#Repository("DAO2") //Focus of question: DAO2 is a repository stereotype
public class DAO2 {
myfind() { ...}
}
Right, so I initialize the App through a springcontext call and then succesfully instantiate a DAO1 instance through the use of #Autowired.
Then I create an unmanaged instance of Job and want to inject "singeltonish" dependencies in that class too by using #Autowired
Both Dao classes are spring stereotypes and scanner finds them fine.
So my question is basically, how should I instantiate the job instance so that I can use #Autowired concept inside it?
If I need a globally accessible applicationcontext, how do I best introduce that?
You can use Spring Bean functionality like injection only in spring managed beans!
But you can use the #Configurable Annotation, but this requires that you use REAL AspectJ.
If a class is annotated by #Configurable (and you use AspectJ) then you can use Springs Injection Annotations even if this class is created by a normal new.
#See
Spring Reference: Chapter 8.8.1 Using AspectJ to dependency inject domain objects with Spring
Spring autowiring using #Configurable
Spring beans are singletons by default. However, what you need there are multiple instances, and on top of that, multiple instances created runtime.
One possibility is to use method injection for this. You'd create a container aware job factory that would request new instances from the container.
(I think it is a bit fishy that you'd need a DAO reference injected in those runtime instances... I would maybe try rethinking the logic. Why couldn't you just provide the DAO reference in a constructor argument, for example, or use it from somewhere else altogether. You could have a method in the dao that would accept a Jobs instance, or a runWith(DAO2 dao) stuff in the JobImpl that would be satisfied with a class injected elsewhere, or a JobProcessor service that would have the daos injected and would ask the relevant info from Jobs instance...)
#Autowired isn't working because your JobImpl object isn't managed by spring so it never has a chance to inject it. Spring managed beans are those where instances are created during component scan or in XML definition. JobImpl in your case is being instantiated as a normal Java object.
One solution would be to replace the auto-wiring with a manual lookup in the spring context.
private DAO2 dao2 = SpringApplicationContext.getApplicationContext ().getBean (DAO2.class);
#Autowired is working in #AppImpl because it's annotated as #Component. This flags it up for spring during its classpath scan where it will create an instance of this class and perform any autowiring/injections.
Add #Component annotation to your JobImpl class. Add component scan for this class in xml and your autowiring will work for dao2(provide getter-setter methods).

request scoped beans in spring testing

I would like to make use of request scoped beans in my app. I use JUnit4 for testing. If I try to create one in a test like this:
#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);
}
With the following bean definition:
<?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>
And I get:
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'
So I found this blog that seemed helpful:
http://www.javathinking.com/2009/06/no-scope-registered-for-scope-request_5.html
But I noticed he uses AbstractDependencyInjectionSpringContextTests which seems to be deprecated in Spring 3.0.
I use Spring 2.5 at this time, but thought it shouldn't be too hard to switch this method to use AbstractJUnit4SpringContextTests
as the docs suggest (ok the docs link to the 3.8 version but I'm using 4.4). So I change the
test to extend AbstractJUnit4SpringContextTests... same message. Same problem. And now the prepareTestInstance() method I want
to override is not defined. OK, maybe I'll put those registerScope calls somewhere else... So I read more about TestExecutionListeners and think that would be better since I don't want to have to inherit the spring package structure. So
I changed my Test to:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
#TestExecutionListeners({})
public class TestScopedBeans {
expecting I would have to create a custom listener but I when I ran it. It works! Great, but why? I don't see where any of the stock listeners
are registering request scope or session scope, and why would they? there's nothing to say I want that yet, this might not be a Test for Spring MVC code...
Solution for Spring 3.2 or newer
Spring starting with version 3.2 provides support for session/request scoped beans for integration testing.
#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));
}
}
Read more: Request and Session Scoped Beans
Solution for Spring before 3.2 with listener
#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 for Spring before 3.2 with custom scopes
#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
}
or with xml configuration
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>
Source code
Source code for all presented solutions:
https://github.com/mariuszs/spring-test-web
I've tried several solutions, including #Marius's solution with the "WebContextTestExecutionListener", but it didn't work for me, as this code loaded the application context before creating the request scope.
The answer that helped me in the end is not a new one, but it's good:
http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope-beans/
I simply added the following snippet to my (test) application context:
<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>
Good luck!
A solution, tested with Spring 4, for when you require request-scoped beans but aren't making any requests 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()));
}
// ...
The test passes because it isn't doing anything :)
When you omit the #TestExecutionListeners annotation, Spring registers 3 default listeners, including one called DependencyInjectionTestExecutionListener. This is the listener responsible for scanning your test class looking for things to inject, including #Resource annotations. This listener tried to inject tObj, and fails, because of the undefined scope.
When you declare #TestExecutionListeners({}), you suppress the registration of the DependencyInjectionTestExecutionListener, and so the test never gets tObj injected at all, and because your test is not checking for the existence of tObj, it passes.
Modify your test so that it does this, and it will fail:
#Test
public void testBean() {
assertNotNull("tObj is null", tObj);
}
So with your empty #TestExecutionListeners, the test passes because nothing happens.
Now, on to your original problem. If you want to try registering the request scope with your test context, then have a look at the source code for WebApplicationContextUtils.registerWebApplicationScopes(), you'll find the line:
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
You could try that, and see how you go, but there might be odd side-effects, because you're not really meant to do this in a test.
Instead, I would recommend rephrasing your test so that you don't need request scoped beans. This shouldn't be difficult, the lifecycle of the #Test shouldn't be any longer than the lifecycle of a request-scoped bean, if you write self-contained tests. Remember, there's no need to test the scoping mechanism, it's part of Spring and you can assume it works.
This is still an open issue:
https://jira.springsource.org/browse/SPR-4588
I was able to get this to work (mostly) by defining a custom context loader as outlined in
http://forum.springsource.org/showthread.php?p=286280
Test Request-Scoped Beans with Spring explains very well how to register and create a custom scope with Spring.
In a nutshell, as Ido Cohn explained, it's enough to add the following to the text context configuration:
<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>
Instead of using the predefined SimpleThreadScope, based on ThreadLocal, it's also easy to implement a Custom one, as explained in the 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;
}
}
MariuszS' solution works, except I couldn't get the transaction committed properly.
It seems the newly released 3.2 has finally made testing request/session scoped beans first class citizens. Here's a couple of blogs for more details.
Rossen Stoyanchev's Spring Framework 3.2 RC1: Spring MVC Test Framework
Sam Brannen's Spring Framework 3.2 RC1: New Testing Features
NOT reading the docs sometimes drives one crazy. Almost.
If you are using shorter-lived beans (request scope for example), you most likely also need to change your lazy init default! Otherwise the WebAppContext will fail to load and tell you something about missing request scope, which is of course missing, because the context is still loading!
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-lazy-init
The Spring guys should definitely put that hint into their exception message...
If you don't want to change the default, there is also the annotation way: put "#Lazy(true)" after #Component etc. to make singletons initialize lazy and avoid instantiating request-scoped beans too early.

Categories

Resources