2011-10-23

JEE6: Integration testing with JBoss 7

Introduction

When your code utilises some of the JEE6 goodies, such as CDI or EJB, then it may become handy if not necessary to execute the tests for this code on the actual server as opposed to running it in a mocked unit test environment. Benefits?
  • Your tests run in the same environment (or similar) as your production code.
  • In certain cases, it actually makes testing possible, because even when using mocking certain JEE6 behaviours are hard to simulate in a unit test.
JBoss 5 was so slow... But with JBoss 7 it doesn't take so much time to start up the server. So tests can run as a part of your Maven build or inside your IDE.

Configuration

Here I will show you how to set it all up. I did it myself, it works, and you can checkout the source yourself from GitHub.

What are we testing?

First, let's take a look at the class under test.
import javax.ejb.Local;
import javax.ejb.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import me.m1key.audiolicious.domain.entities.Song;
import me.m1key.audiolicious.services.SongRepository;

@Singleton
@Local(SongRepository.class)
public class JpaSongRepository implements SongRepository {

   @PersistenceContext
   private EntityManager entityManager;

   @Override
   public void save(Song song) {
       entityManager.persist(song);
   }
}
As you can see, it does not do a lot - but it is an EJB and it uses @PersistenceContext. I did not expose any way to set the entityManager because I don't like having to change production code just so that tests can run. So, that would be rather hard to mock. But we don't have to mock, we are going to run the test on the server.

The test

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.IOException;
import java.util.Date;

import javax.inject.Inject;

// Certain imports omitted for brevity.

import org.jboss.arquillian.container.test.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.WebArchive;
import org.jboss.shrinkwrap.resolver.api.DependencyResolvers;
import org.jboss.shrinkwrap.resolver.api.maven.MavenDependencyResolver;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class JpaSongRepositoryIT {

   // Stuff omitted for brevity.
   
   private Date albumDateAdded = new Date();
   private Date albumDateModified = new Date();
   private Date albumDateSkipped = new Date();

   @Inject
   private SongRepository jpaSongRepository;
   @Inject
   private RepositoriesTestHelperBean testHelperBean;

   @Deployment
   public static WebArchive createTestArchive()
           throws IllegalArgumentException, IOException {
       return ShrinkWrap
               .create(WebArchive.class,
                       JpaSongRepositoryIT.class.getSimpleName() + ".war")
               .addAsWebInfResource(EmptyAsset.INSTANCE,
                       ArchivePaths.create("beans.xml"))
               .addAsResource("log4j.xml", "log4j.xml")
               .addAsResource("META-INF/persistence.xml",
                       "META-INF/persistence.xml")
               .addClasses(Album.class, Artist.class, JpaSongRepository.class,
                       NullAlbum.class, NullArtist.class,
                       NullEntitiesFactory.class, Rating.class,
                       RatingTo.class, Song.class, SongRepository.class,
                       SongTo.class, RepositoriesTestHelperBean.class,
                       TrackTo.class)
               .addAsLibraries(
                       DependencyResolvers
                               .use(MavenDependencyResolver.class)
                               .artifacts("org.slf4j:slf4j-api:1.6.1",
                                       "org.slf4j:slf4j-log4j12:1.6.1",
                                       "commons-lang:commons-lang:2.6")
                               .resolveAsFiles());
   }

   @Test
   public void shouldCreateAndRetrieveSong() {
       assertEquals("There should be no songs before any are created.",
               Long.valueOf(0), testHelperBean.totalSongs());

       Artist artist = testHelperBean.createArtist(ARTIST_NAME);
       Album album = testHelperBean.createAlbum(ALBUM_NAME, artist,
               new Rating(80));
       Song song = new Song(SONG_1_NAME, ARTIST_NAME, album, 1988,
               "Zakk Wylde/Bob Daisley/Ozzy Osbourne", "Rock", albumDateAdded,
               albumDateModified, new Rating(80), 9, albumDateSkipped, 0,
               false, 0, 0, false);
       jpaSongRepository.save(song);

       assertNotNull("Saved song should not be null.",
               testHelperBean.getSongByUuid(song.getUuid()));
   }
  
   // Other tests omitted for brevity.
}
Let's take a closer look. First, the test is annotated with @RunWith(Arquillian.class). That is because we need Arquillian to orchestrate the entire test. Arquillian will handle our @Deployment. This is where, using ShrinkWrap, we create the WAR file with our test and what we want to test with all its dependencies, also the external ones, from our pom.xml. This WAR file will be deployed to JBoss 7 when you run the test with Maven or from within your IDE. I must admit that getting the dependencies right (i.e. giving it exactly what it needs and not too much) can be a bit tiresome and frustrating.

Dependencies

Below are the relevant dependencies.
       <dependency>
           <groupId>javax.inject</groupId>
           <artifactId>javax.inject</artifactId>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.spec</groupId>
           <artifactId>jboss-javaee-web-6.0</artifactId>
           <type>pom</type>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.as</groupId>
           <artifactId>jboss-as-arquillian-container-managed</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.shrinkwrap</groupId>
           <artifactId>shrinkwrap-api</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.shrinkwrap.resolver</groupId>
           <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.arquillian.junit</groupId>
           <artifactId>arquillian-junit-container</artifactId>
           <scope>test</scope>
       </dependency>

arquillian.xml

You also need to provide an arquillian.xml file in your src/test/resources. Below is my entire file.
<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://jboss.org/schema/arquillian
       http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

   <!-- Uncomment to have test archives exported to the file system for inspection -->
   <engine>
       <property name="deploymentExportPath">target/</property>
   </engine>

   <container qualifier="jboss" default="true">
       <protocol type="jmx-as7">
           <property name="executionType">REMOTE</property>
       </protocol>
   </container>

</arquillian>
When the test is running, it's running on a remote instance of JBoss. That means you have to download JBoss 7 yourself. You can hardcode its location in this file, but it's not a good idea if you're sharing the project with other people. Instead, you can set your JBOSS_HOME system variable to something like e:\Servers\jboss-as-web-7.0.0.Final.

Download sample source code

As always, working code is available for you to download. Get it now from GitHub. There is a text file called SETUP that explains how to get it going (the database and so on). If you have any questions, feel free to post them here and I will do my best to answer.

14 comments:

  1. Isn't it too early for JEE7? :)

    ReplyDelete
  2. Ha! I forgot to mention I am publishing this post from the future.

    Fixed, thanks. :)

    ReplyDelete
  3. Ah, so you bought this one? http://otomoto.eu/de-lorean-dmc-12-powrot-do-przyszlosci-C20647286.html

    ReplyDelete
  4. I can't tell you without causing a serious paradox! Sorry. :) Unless the other quantum interpretation is right... Hmm...

    ReplyDelete
  5. If you are a beginner, a java code helper could be your best friend. Whenever you want to learn codes or if you are looking for an idea what to put in your program, you can easily take ideas from this documentation. Here you will find all the details regarding the software you are working on. You can borrow ideas and codes as per your requirements. It is like your java assignment helper. Sometimes, the documentation holds so much information that the user gets flabbergasted. In such a situation, you can always search the internet for any kind of information or hint! So search the internet for any kind of java assignment help.

    ReplyDelete
  6. Canon IJ Network Tool will get you through the network settings uninterruptedly. It is essentially required when you are attempting to get your printer connected to a different network because a new network tends to reset the printer’s existing network settings.The Canon IJ Printer Utility can be used to keep a check on your printer’s ink levels and cartridges and clean the ink tanks and paper feed rollers. Also, you can make adjustments to your Canon printer’s power settings.

    ReplyDelete
  7. The ij.start.cannon setup process for every Canon model is almost similar, however the download through https //ij.start.cannon and http //ij.start.cannon installation process may differ. you can also visit canonsetup-canon.com/ijsetup website for same. Https //ij.start.cannon.Depending on your requirement, it offers a type printer including PIXMA, SELPHY, MAXIFY, etc. canon.com/ijsetup

    ReplyDelete
  8. Once you are done with the driver setup via canon.com/ijsetup , you will have to insert a pile of pages into the printer tray for printing the documents..

    Visit ij.start.canon | ij.start canon and find out the best way to download Canon printer drivers.

    All-in-one Canon Inkjet printers are suitable for home, business, school, and others to improve productivity. You can easily set up your Canon printer through drivers from Canon.com/ijsetup , wireless connection, USB, and a few components.

    ReplyDelete
  9. Meanwhile, choosing to operate in Document Mode, you ensure the improved readability of your documents as a result of scanning the document. Similarly, in Photo Mode, you can get the best quality of your photos scanned. ij scan utility | ij canon scan utility

    ReplyDelete
  10. Nice...
    It's a very powerful article. I really like this post. Thank you so much for sharing this.

    ij.start.cannon
    ij.start.canon

    ReplyDelete
  11. Amazing website, Love it. Great work done. Nice website. Love it. This is really nice.

    FMovies | FMovies

    ReplyDelete
  12. Nice post. Thanku so much to share this with us.

    putlocker
    kissanime

    ReplyDelete