Rollback transaction after #Test - java

First of all, I've found a lot of threads on StackOverflow about this, but none of them really helped me, so sorry to ask possibly duplicate question.
I'm running JUnit tests using spring-test, my code looks like this
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {})
public class StudentSystemTest {
#Autowired
private StudentSystem studentSystem;
#Before
public void initTest() {
// set up the database, create basic structure for testing
}
#Test
public void test1() {
}
...
}
My problem is that I want my tests to NOT influence other tests.
So I'd like to create something like rollback for each test.
I've searched a lot for this, but I've found nothing so far.
I'm using Hibernate and MySql for this

Just add #Transactional annotation on top of your test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"testContext.xml"})
#Transactional
public class StudentSystemTest {
By default Spring will start a new transaction surrounding your test method and #Before/#After callbacks, rolling back at the end. It works by default, it's enough to have some transaction manager in the context.
From: 10.3.5.4 Transaction management (bold mine):
In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener. Note that TransactionalTestExecutionListener is configured by default, even if you do not explicitly declare #TestExecutionListeners on your test class. To enable support for transactions, however, you must provide a PlatformTransactionManager bean in the application context loaded by #ContextConfiguration semantics. In addition, you must declare #Transactional either at the class or method level for your tests.

Aside: attempt to amend Tomasz Nurkiewicz's answer was rejected:
This edit does not make the post even a little bit easier to read, easier to find, more accurate or more accessible. Changes are either completely superfluous or actively harm readability.
Correct and permanent link to the relevant section of documentation about integration testing.
To enable support for transactions, you must configure a PlatformTransactionManager bean in the ApplicationContext that is loaded via #ContextConfiguration semantics.
#Configuration
#PropertySource("application.properties")
public class Persistence {
#Autowired
Environment env;
#Bean
DataSource dataSource() {
return new DriverManagerDataSource(
env.getProperty("datasource.url"),
env.getProperty("datasource.user"),
env.getProperty("datasource.password")
);
}
#Bean
PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
In addition, you must declare Spring’s #Transactional annotation either at the class or method level for your tests.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Persistence.class, SomeRepository.class})
#Transactional
public class SomeRepositoryTest { ... }
Annotating a test method with #Transactional causes the test to be run within a transaction that will, by default, be automatically rolled back after completion of the test. If a test class is annotated with #Transactional, each test method within that class hierarchy will be run within a transaction.

The answers mentioning adding #Transactional are correct, but for simplicity you could just have your test class extends AbstractTransactionalJUnit4SpringContextTests.

I know, I am tooooo late to post an answer, but hoping that it might help someone. Plus, I just solved this issue I had with my tests. This is what I had in my test:
My test class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "path-to-context" })
#Transactional
public class MyIntegrationTest
Context xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
I still had the problem that, the database was not being cleaned up automatically.
Issue was resolved when I added following property to BasicDataSource
<property name="defaultAutoCommit" value="false" />
Hope it helps.

In addition to adding #Transactional on #Test method, you also need to add #Rollback(false)

You need to run your test with a Spring context and a transaction manager, e.g.,
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"/your-applicationContext.xml"})
#TransactionConfiguration(transactionManager="txMgr")
public class StudentSystemTest {
#Test
public void testTransactionalService() {
// test transactional service
}
#Test
#Transactional
public void testNonTransactionalService() {
// test non-transactional service
}
}
See chapter 3.5.8. Transaction Management of the Spring reference for further details.

You can disable the Rollback:
#TransactionConfiguration(defaultRollback = false)
Example:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#Transactional
#TransactionConfiguration(defaultRollback = false)
public class Test {
#PersistenceContext
private EntityManager em;
#org.junit.Test
public void menge() {
PersistentObject object = new PersistentObject();
em.persist(object);
em.flush();
}
}

Related

Service class's #Transactional not invoked in test context

I am using Spring's #Transactional in my DAO's service class. The application has beans configured in XML(s) and via annotations like #Service.
My test case configuration looks like below:
#RunWith(SpringJunit4ClassRunner.class)
#ContextHierarchy({
#ContextConfiguration(locations = {"classpath:spring/spring.xml"}),
#ContextConfiguration(classes = {Service.class})
})
#ComponentScan(...)
public class TestRunner {
#Autowired
private Service service;
#Test
public void testSave() {
service.save(...);
}
}
I have below configured in XML:
<tx:annotation-driven transaction-manager="txmManager"/>
<!-- bean for hibernate5 transaction manager using sessionfactory -->
The service class looks as below:
#Service
public class Service {
#Autowired
private DAO dao;
#Transactional
public Entity save(Entity entity) {
dao.save(entity);
}
}
Now it all works perfectly during source context run but when I run the test cases, the transaction is never invoked. Note: there is no NPE on calling Service.class's method.
I tweaked around and noticed that if I create a bean for Service.class in my (test) spring xml file, the test cases work as expected, i.e., the transaction manager is called and the in-memory database is updated.
I expected SpringJunit4ClassRunner to create a MergedContext and the transaction-manager configuration to be automatically kicked in upon Service#save call.
Any thoughts on what am I missing here?
Hibernate - 5.2.6.Final,
Spring - 4.2.0.RELEASE,
Spring-test - 4.1.6.RELEASE

Spring Boot Unit Testing - Test fails complaining about not having an "entityManagerFactory" bean defined

I'm trying to write a unit test for a Controller in a Spring Boot application. The application runs smoothly, my problem is with running its tests.
Here is the test code:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
#AutoConfigureTestEntityManager
public class MyControllerTest {
#Autowired
private MockMvc mockMvc;
#Mock
private MyRepository myRepository;
#Mock
ZendeskNotifier zendeskNotifier;
#Mock
ActivityLogger activityLogger;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void cannotSendFooWithoutMessageBody() throws Exception {
this.mockMvc.perform(post("/api/v1/foo/1/send"))
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(content().string(containsString("The message body cannot be empty.")));
}
}
When I try running it I get:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field jobEventRepository in foo.bar.util.memsource.svc.MemsourceEventProcessor required a bean named 'entityManagerFactory' that could not be found.
Action:
Consider defining a bean named 'entityManagerFactory' in your configuration.
And that feels weird to me, since I'm providing the AutoConfigureTestEntityManager annotation and would expect all the EntityManager-related stuff to be in place.
If Google brought you here and you're using Spring Boot, you may need to add #DataJpaTest to your test class. It's found in org.springframework.boot:spring-boot-test-autoconfigure. You may also discover upon rerunning that you need to declare a dependency on org.hibernate:hibernate-validator.
Inject Spring's TestEntityManager in your mockito test class.
#Autowired
private TestEntityManager entityManager;
You are already using #AutoConfigureTestEntityManager on the test class to auto configure this test entity manager. So you don't have to do anything else in the config file.
Spring Boot is loading your application's configuration, causing your data layer to be initialized.
Excerpt from Spring Boot's docs, Detecting Test Configuration:
Spring Boot’s #*Test annotations search for your primary configuration
automatically whenever you do not explicitly define one.
The search algorithm works up from the package that contains the test
until it finds a class annotated with #SpringBootApplication or
#SpringBootConfiguration. As long as you structured your code in a
sensible way, your main configuration is usually found.
When the scanning hits your main class it is likely finding an annotation like #EnableJpaRepositories, which initializes your data layer and thus requires the entity manager factory. (You may see other side effects too, like Hibernate initializing your in-memory database if your application uses one.)
As other answers suggest, you could initialize the data layer, or try to wire/mock the missing beans. But since you aren't testing the data layer here it would be better to control the test's configuration.
The docs suggest some solutions:
Move #EnableJpaRepositories from the main class to a config class in child package. It will get scanned by the application (from the top package down) but not by the unit tests. This is discussed in the section User Configuration and Slicing.
Add a nested #Configuration class to override the configuration used by the unit test.
The first one seems like a good rule to follow in general. Other annotations like #EnableBatchProcessing and #EnableScheduling can be treated the same way, which will speed up your unit tests.
you need to configure entityManagerFactory, you can refer bellow code
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="packagesToScan" value="org.demo.test.entity" />
<property name="dataSource" ref="dataSource" />
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
</props>
</property>
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider">
</bean>
</property>
</bean>
Move #EnableJpaRepositories from the main class to a config class in a child package. It will get scanned by the application (from the top package down) but not by the unit tests.
#Configuration
#EnableJpaRepositories(
value = "com.company.repository"
)
public class JpaConfig {
}
I found out that there is an Annotation you can use to add JPA-Support to your WebMVCTest (#AutoConfigureDataJpa)
#ContextConfiguration(classes = { SurchargesTestConfiguration.class })
#WebMvcTest(SurchargesApiController.class)
#AutoConfigureDataJpa
#ActiveProfiles("local")
class SurchargesApiControllerTest {
#Autowired
private MockMvc mvc;
#Test
void testNotFound() throws Exception {
mvc.perform(get("/surcharges/marketnumber") //
.headers(getDefaultHeaders())) //
.andExpect(status().isNotFound());
}
}

Spring Test's MockMvc and Spring Remoting's HttpInvoker

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.

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

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