Version 4

    This tutorial deals with a class of testing somewhere between simple unit testing and full blown integration testing. The SE mode of Weld offers us the opportunity to test some components that need injected resources without firing up an EE container.

     

    These examples can all be run with only the weld-1.0.0 code, a JEE6 container is not required. The attached project is all you need if you are running maven and optionally eclipse. If not you will need to use the artifact jars from the weld-1.0.0 release.

     

    Note that the weld-se sub-project is pre-production, but candidate releases are now available. All the weld and weld-se artifacts are available in the Maven 2 central repository.

     

    The tutorial's domain model is a room and a light source. The light source object will be injected into the room object and we will attempt to configure the light source before it is injected. Our first room is the Kitchen:

     

    public class Kitchen implements Room {
        @Inject private KitchenLamp light;
        public String getLightState() {
            return light.getState();
        }
    }

     

    KitchenLamp is a simple subclass of LightSource:

     

    public class LightSource {
        String state = "dark";
        public String getState() {
            return state;
        }
        public void setState(String state) {
            this.state = state;
        }
    }

     

    And lastly a class to create a Kitchen object and test it:

     

    public class Kitchen1Test {
        private BeanManager beanManager;
        @BeforeClass
        public void Setup() {
            StartMain startMain = new StartMain(new String[0]);
            beanManager = startMain.go();
        }
        @Test
        public void test1() throws Exception {
            Bean roomBean = (Bean)beanManager.getBeans(Kitchen.class).iterator().next();
            CreationalContext cc = beanManager.createCreationalContext(roomBean);
            Kitchen room = (Kitchen)beanManager.getReference(roomBean, Kitchen.class, cc);
            Assert.assertEquals(room.getLightState(), "dark");
        }
    }

     

    Running Kitchen1Test succeeds. Nowhere did we set the light field in Kitchen class, the Weld BeanManager did that for us.

    So what is happening here? StartMain bootstraps a Weld BeanManager implementation. The BeanManger looks for a beans.xml file in META-INF and on finding one it examines each class to find Bean candidates. All our domain model classes qualify. We ask the BeanManager for a Kitchen.class bean and what we get back is not an instance of our Kitchen bean but a meta-bean holding information about how the CDI container manages instances of the bean. BeanManager.getReference() actually creates or finds a bean instance for us.

     

    Now as part of our testing we want to turn the lamp on. We try this:

     

    public class Kitchen2Test {
        private BeanManager beanManager;
        @BeforeClass
        public void Setup() {
            StartMain startMain = new StartMain(new String[0]);
            beanManager = startMain.go();
        }
        @Test
        public void test1() throws Exception {
            Bean lightBean = (Bean)beanManager.getBeans(KitchenLamp.class).iterator().next();
            CreationalContext lightCc = beanManager.createCreationalContext(lightBean);
            KitchenLamp light = (KitchenLamp)beanManager.getReference(lightBean, KitchenLamp.class, lightCc);
            light.setState("bright");
           
            Bean kitchenBean = (Bean)beanManager.getBeans(Kitchen.class).iterator().next();
            CreationalContext roomCc = beanManager.createCreationalContext(kitchenBean);
            Kitchen room = (Kitchen)beanManager.getReference(kitchenBean, Kitchen.class, roomCc);
            Assert.assertEquals(room.getLightState(), "bright");
        }
    }

     

    Running Kitchen2Test fails. Oops! Our KitchenLamp class has no annotation, so it is in "default" scope, and Weld creates a new instance of the bean every time we ask it for a reference. So we can only turn the lamp on from *inside* the kitchen. That's not what we wanted. So let's consider a different scenario. We have a beautiful 19th century conservatory, and its only light source is the sun. Now there is only one sun for all the conservatories in everyone's garden, so we model it like this:

     

    @ApplicationScoped
    public class Sun extends LightSource {}

     

    Our conservatory test looks just like the second kitchen test:

     

    public class ConservatoryTest {
        private BeanManager beanManager;
        @BeforeClass
        public void Setup() {
            StartMain startMain = new StartMain(new String[0]);
            beanManager = startMain.go();
        }
        @Test
        public void test1() throws Exception {
            Bean lightBean = (Bean)beanManager.getBeans(Sun.class).iterator().next();
            CreationalContext lightCc = beanManager.createCreationalContext(lightBean);
            Sun light = (Sun)beanManager.getReference(lightBean, Sun.class, lightCc);
            light.setState("bright");
           
            Bean roomBean = (Bean)beanManager.getBeans(Conservatory.class).iterator().next();
            CreationalContext roomCc = beanManager.createCreationalContext(roomBean);
            Conservatory room = (Conservatory)beanManager.getReference(roomBean, Conservatory.class, roomCc);
            Assert.assertEquals(room.getLightState(), "bright");
        }
    }

     

    This time ConservatoryTest succeeds. When we got a reference to Sun the BeanManager created a bean instance and put it in the ApplicationContext. Then when we got a reference to Conservatory the BeanManager created a new Conservatory instance and injected the existing instance of Sun (from ApplicationContext) with our configuration.

     

    Now we turn to a slightly different scenario. Our attic has no fixed light, so the only light source is a torch carried by a person making a visit to the attic. We might model this as a SessionScoped bean:

     

    @SessionScoped
    public class Torch extends LightSource implements Serializable {}

     

    Session scope is long lived like application scope, but there may be lots of them, so the container may have to passivate the objects in session context. Consequently Torch must implement Serializable. Let's try a test like ConservatoryTest:

     

    public class Attic1Test {
        private BeanManager beanManager;
        @BeforeClass
        public void Setup() {
            StartMain startMain = new StartMain(new String[0]);
            beanManager = startMain.go();
        }
        @Test
        public void test1() throws Exception {
            Bean lightBean = (Bean)beanManager.getBeans(Torch.class).iterator().next();
            CreationalContext lightCc = beanManager.createCreationalContext(lightBean);
            Torch light = (Torch)beanManager.getReference(lightBean, Torch.class, lightCc);
            light.setState("bright");
           
            Bean roomBean = (Bean)beanManager.getBeans(Attic.class).iterator().next();
            CreationalContext roomCc = beanManager.createCreationalContext(roomBean);
            Attic room = (Attic)beanManager.getReference(roomBean, Attic.class, roomCc);
            Assert.assertEquals(room.getLightState(), "bright");
        }
    }

     

    Attic1Test fails with an exception:

     

    javax.enterprise.context.ContextNotActiveException: No active contexts for scope type javax.enterprise.context.SessionScoped

     

    Sessions are managed by the web container in an application server like JBoss. So a test program must also manage sessions. For a single thread unit test it is sufficient to create one session context and register it with the BeanManager. A session context must be activated to be used, and it must also be provided with a BeanStore in which to keep bean instances. Attic test now looks like:

     

    public class Attic2Test {
        private BeanManager beanManager;
        @BeforeClass
        public void Setup() {
            StartMain startMain = new StartMain(new String[0]);
            beanManager = startMain.go();
        }
        @Test
        public void test1() throws Exception {
            BeanManagerImpl bmImpl = (BeanManagerImpl)beanManager;
            SessionContext sc = new SessionContext();
            sc.setActive(true);
            sc.setBeanStore(new HashMapBeanStore());
            bmImpl.addContext(sc);
           
            Bean lightBean = (Bean)beanManager.getBeans(Torch.class).iterator().next();
            CreationalContext lightCc = beanManager.createCreationalContext(lightBean);
            Torch light = (Torch)beanManager.getReference(lightBean, Torch.class, lightCc);
            light.setState("bright");
           
            Bean roomBean = (Bean)beanManager.getBeans(Attic.class).iterator().next();
            CreationalContext roomCc = beanManager.createCreationalContext(roomBean);
            Attic room = (Attic)beanManager.getReference(roomBean, Attic.class, roomCc);
            Assert.assertEquals(room.getLightState(), "bright");
        }
    }

     

    This time Attic2Test succeeds. The Torch bean instance is stored in the bean store we created and subsequently injected into the attic instance.

     

    There are two more normal built-in scopes: request scope and conversation scope. For the purposes of testing converstaion scope can be treated the same as session scope, and request scope is the same except that it is a non-passivating scope (ie assumed to be short lived) and request scoped objects do not need to be Serializable.

     

    BeanManager is part of the JSR299 Service Provider Interface (SPI) common to all CDI implementations. BeanManagerImpl is the Weld implementation of BeanManager and StartMain is a Weld utility to bootstrap a BeanManager with an SE profile. Looking at the standard output from the test classes you will see that Weld tells us that Transactional services are not available. EE services such as JPA and JTA are also not available, but many lower level injection dependent testing scenarios work fine without them.

     

    Part 2 deals with testing a session bean using a mock EntityManager

     

    Note if running this tutorial within Eclipse you need to run at least a maven compile before attempting any of the tests so that the beans.xml file is copied to the target drectory.