Friday, July 1, 2011

Integration testing scoped beans in CDI 1.0 and Spring 3.1

In this blog post I describe how to do integration testing with scoped beans in Spring and CDI. Everything is illustrated with small code samples. Integration testing with scopes is not particular easy. Imagine a bean that lives in the session scope, like 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 read my other blog to learn some 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. Also notice that I have registered my custom scope from the example of my other blog.

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.

Changes to your pom.xml file

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

Writing and executing a test with Arquillian and JUnit

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.

More error messages during Arquillian set-up

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!

3 comments:

  1. Hey

    Just a few notes on your Arquillian Setup:

    * since your testing on a GlassFish Server, you do not need the JBoss AS Client libraries(which is the lib that pulls in all the deps your talking about, org.jboss.jbossas:jboss-as-client)

    * jndi.properties is not used for the GlassFish Remote Container.
    (jndi.properties was used in Arquillian pre Alpha5 for the JBoss AS Containers, but that has been moved to arquillian.xml. See container configuration, https://docs.jboss.org/author/display/ARQ/Complete+Container+Reference).

    * There are alternative ways of adding classes to your ShrinkWrap Archives, a possible rewrite of your @Deployment could be:

    ShrinkWrap.create(JavaArchive.class)
    .addPackages(true /* recurrsive */, MyScopeBean.class.getPackage())
    .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");

    ShrinkWrap has a MavenDependencyResolver extension as well, to pull Artifacts directly from your local/ remote repositories:

    ShrinkWrap.create(WebArchive.class)
    .addAsLibraries(
    DependencyResolvers.use(MavenDependencyResolver.class)
    .artifact("my.favorite.lib:super-lib:1.0.0")
    .resolveAs(GenericArchive.class));

    -aslak-

    ReplyDelete
  2. Hi Aslak,

    thy for the valuable comment.
    I'll look into that tommorow morning and make the necessary changes to the article if required.

    Cheers,
    Niklas

    ReplyDelete
  3. It’s a very useful & qualitative information shared on integration testing. Indeed a nice share.
    Keep it up! :)

    ReplyDelete