Integration testing scoped beans in CDI 1.0 and Spring 3.1
UserCredentials
. In an integration test you typically have no HttpRequest or HttpSession to work on (at least if you are not doing tests that include your user interface). Therefore you need some infrastructure for integration testing. With both technologies it is a little puzzling to get this infrastructure going. Get your own picture of it.If you are new to scope and context in CDI and Spring check out the basics and get an overview about the different scopes.
Integration testing scoped beans in Spring
In Spring 3.1 there is no integration test support for scoped session or request beans (see here). It is scheduled for Spring Version 3.2. However, this link explains a solution that worked for me.
First you need to develop a SessionScope for the test. It’s purpose is to Mock a HttpRequest and a HttpSession.
package com.mycompany.springapp.scope; import org.springframework.beans.factory.InitializingBean; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.SessionScope; public class SetupSession extends SessionScope implements InitializingBean { public void afterPropertiesSet() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpSession session = new MockHttpSession(); request.setSession(session); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes( request)); } }
To register that class as the session scope management object in your test-beans.xml
do this:
Notice that I registered the scopes after the context:component-scan
tag.
Finally, I wrote my test class:
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.Assert; @ContextConfiguration("/test-beans.xml") @RunWith(SpringJUnit4ClassRunner.class) public class MyScopeBeanTest { @Autowired private MyScopeBean myScopeBean; @Test public void testBeanScopes() { Assert.isTrue(myScopeBean.getMyCustomScopedService().getName().equals("Test")); Assert.isTrue(myScopeBean.getMySessionScopedService().getName().equals("Test")); } }
Notice that I have called a method getName()
on the scoped bean. This is necessary to ensure that scoping works. The client proxy may get injected at the injection point, but if you make a call to the proxy it does not have a reference to a scope object and a collaborating object respectively.
Integration testing scoped beans with CDI
The tool I used for integration testing CDI is Arquillian. There are alternatives. You could use Weld “natively” if you only test with CDI classes. But if you also have EJB, that’s not sufficient. Arquillian comes with a reasonable amount of transitive dependencies. Let’s see how to get the stuff going.
Note: without Maven you’re lost in the desert here, so I encourage you to use it! I have tried m2eclipse for Helios, it did not work for me, I went back to good old command line using Maven 3.
pom.xml
fileThese samples assume you have a Java EE project working, you can also see here how to set-up a new Java EE 6 project. To integrate Arquillian make the following changes to your pom.xml
file:
In the properties section:
1.0.0.Alpha5
Add this repository:
repository.jboss.org http://repository.jboss.org/nexus/content/groups/public default true never warn false always warn
This is the official JBoss Maven repository where all the Arquillian distributions are available.
Add the following dependencies to your pom.xml
:
junit junit 4.8.1 test org.jboss.arquillian arquillian-junit ${arquillian.version} test org.jboss.arquillian.container arquillian-glassfish-remote-3.1 ${arquillian.version} test javax.enterprise cdi-api 1.0-SP4 test
The first dependency is your JUnit framework to write integration tests. The second dependency integrates Arquillian with JUnit. The third dependency integrates your deployment container. For me that is my Glassfish installation. The last dependency is the CDI API that needs to be available for your CDI tests.
Notice in the line 17, I am using my Glassfish 3.1 installation as deployment container and Arquillian uses remote calls to perform the tests. You need to configure your own deployment environment here. See the JBoss Maven Repo for the correct artifactId
value. With Arquillian your target environment can also be an embedded container such as JBoss Embedded AS, GlassFish Embedded or Weld SE. In that case, you don’t need a seperate container installation and remote calls, all is running locally (“in-memory”).
You do a mvn eclipse:eclipse after you added the dependencies for your target environment.
Finally I wrote my first Arquillian integration test class:
import javax.inject.Inject; import junit.framework.Assert; import org.jboss.arquillian.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ArchivePaths; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.Test; import org.junit.runner.RunWith; import com.mycompany.jeeapp.scope.MyApplicationService; import com.mycompany.jeeapp.scope.MyConversationService; import com.mycompany.jeeapp.scope.MyDefaultService; import com.mycompany.jeeapp.scope.MyRequestService; import com.mycompany.jeeapp.scope.MyScopeBean; import com.mycompany.jeeapp.scope.MySessionService; import com.mycompany.jeeapp.scope.MySingletonService; import com.mycompany.jeeapp.scope.extension.MyCustomScopeService; @RunWith(Arquillian.class) public class MyArquillianJUnitTest { @Inject private MyScopeBean myScopeBean; @Deployment public static JavaArchive createTestArchive() { return ShrinkWrap .create(JavaArchive.class, "test.jar") .addClasses(MyScopeBean.class,MyApplicationService.class, MyConversationService.class, MyDefaultService.class, MyRequestService.class, MySessionService.class, MySingletonService.class, MyCustomScopeService.class) .addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml")); } @Test public void testScopedBeans() { Assert.assertTrue(myScopeBean.getApplicationService().getSomeName() .equals("myName")); Assert.assertTrue(myScopeBean.getApplicationServiceWithNew().getSomeName() .equals("myName")); Assert.assertTrue(myScopeBean.getCustomScopeService().getSomeName() .equals("myName")); Assert.assertTrue(myScopeBean.getDefaultService().getSomeName() .equals("myName")); Assert.assertTrue(myScopeBean.getRequestService().getSomeName() .equals("myName")); Assert.assertTrue(myScopeBean.getSessionService().getSomeName() .equals("myName")); Assert.assertTrue(myScopeBean.getSingletonService().getSomeName() .equals("myName")); } }
Conclusion
Spring does not offer an integrated test support for scoped beans at the moment. This was very surpising as Spring always attached major importance to all test topics. There is a workaround that I have described in my blog. It wasn’t difficult to make that work. Full integration test support is scheduled for Release 3.2 M1.
CDI scoped beans testing is enabled with Arquillian. I had some problems during set-up (see last paragraph below) which I think is usual if you use a new technology. The fact that you have to pass all the beans under test to the archive (see @Deployment
method) is something I need to try in a large project: is that really a good idea? Sometimes, large applications are wired together with dozens of beans from different packages. It’s difficult to predict which beans are used in an integration test.
Problems and solutions
Some Arquillian set-ups come with so many dependencies that you cannot use standard Eclipse launch configurations. The command line argument that is generated exceeds the Windows length limit for command line instructions. Therefore I have used an Ant-Script to start my test. The script is just for illustration. You have to build your own own Ant script. You can get your classpath information as follows: in Eclipse, go to “File > Export > General > Ant buildfiles” to generate your classpath information. Take this classpath info and drop it into your Ant JUnit test start script. I have documented my complete Ant script here.
When I started this Ant script then everything worked fine for me. If you have any issues let me know, you can look into your test results file and into the server.log
to analyse.
WELD-001303 No active contexts for scope type javax.enterprise.context.ConversationScoped
-> ConversationScope is bound to JSF by EE spec. So they won’t be active during a normal HTTP request which is what Arquillian is piggybacking on.
POST http://localhost:4848/management/domain/applications/application returned a response status of 403
-> 404/403 errors could be deployment issues, check server.log for the root cause (mine was that I did not have all the required classes added to the test.jar
)
Exception occurred executing command line.
Cannot run program “D:\dev_home\java-6-26\bin\javaw.exe” (in directory “D:\dev_home\repositories\git\jee-app-weld\jee-app-weld”): CreateProcess error=87, Falscher Parameter
-> Classpath exceeds allowed length for windows command line operations. You need to use an Ant script or Maven to run the tests.
ValidationException: DeploymentScenario contains targets not maching any defined Container in the registry
-> see here.
WELD-000072 Managed bean declaring a passivating scope must be passivation capable. Bean: Managed Bean [class com.mycompany.jeeapp.scope.example.UserCredentials] with qualifiers [@Any @Default]
-> You need to implement Serializable on Session- and Conversation-Scoped beans.
DeploymentScenario contains targets not maching any defined Container in the registry. _DEFAULT_
-> see here.
java.net.ConnectException: Connection refused: connect
-> Your remote Java EE server installation is not running, start it!
Reference: “Integration testing scoped beans in CDI 1.0 and Spring 3.1” from our JCG partner Niklas.