1. Overview

Arquillian is a container-agnostic integration testing framework for Jakarta EE. Using Arquillian minimizes the burden of managing containers, deployments, framework initializations, and so on.

We can focus on writing actual tests and not on bootstrapping the test environment.

2. Core Concepts

2.1. Deployment Archives

There is an easy way for testing our application when running inside a container.

Firstly, ShrinkWrap class provides an API to create deployable *.jar, *.war, and *.ear files.

Then, Arquillian allows us to configure the test deployment using the @Deployment annotation – on a method that returns a ShrinkWrap object.

2.2. Containers

Arquillian distinguishes three different types of containers:

  • Remote – tested using a remote protocol like JMX
  • Managed – remote containers but their lifecycle is managed by Arquillian
  • Embedded – local containers where tests are performed using local protocols

Also, we can classify containers by their capabilities:

  • Jakarta EE applications deployed on an application server like Glassfish or JBoss
  • Servlet containers deployed on Tomcat or Jetty
  • Standalone containers
  • OSGI containers

It examines the runtime classpath and automatically selects the available container.

2.3. Test Enrichment

Arquillian enriches tests by providing e.g. the dependency injection so that we can write our tests easily.

We can inject dependencies using @Inject, inject resources with @Resource, EJB session beans using @EJB, etc.

2.4. Multiple Test Runners

We can create multiple deployments using the annotation:

@Deployment(name="myname" order = 1)

Where the name is the name of the deployment file and the order parameter is the execution order of the deployment, so we can now run tests on multiples deployments at the same time using the annotation:

@Test @OperateOnDeployment("myname")

The before test is executed on the myname deployment container using the order defined in the @Deployment annotation.

2.5. Arquillian Extensions

Arquillian offers multiple extensions in case our testing needs aren’t covered by the core runtime. We have persistence, transactions, client/server, REST extensions, etc.

We can enable those extensions by adding appropriate dependencies to Maven or Gradle config files.

Commonly used extensions are Drone, Graphene, and Selenium.

3. Maven Dependencies and Setup

Let’s add the following dependency to our pom.xml file:

<dependency>
    <groupId>org.jboss.arquillian</groupId>
    <artifactId>arquillian-bom</artifactId>
    <version>1.1.13.Final</version>
    <scope>import</scope>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.glassfish.main.extras</groupId>
    <artifactId>glassfish-embedded-all</artifactId>
    <version>4.1.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.jboss.arquillian.container</groupId>
    <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
    <version>1.0.0.Final</version>
    <scope>test</scope>
</dependency>

The latest version of the dependencies can be found here: arquillian-bom, org.glassfish.main.extras, org.jboss.arquillian.container.

4. Simple Test

4.1. Create a Component

Let’s start with a simple component. We do not include any advanced logic here to be able to focus on tests:

public class Component {
    public void sendMessage(PrintStream to, String msg) {
        to.println(message(msg));
    }

    public String message(String msg) {
        return "Message, " + msg;
    }
}

Using Arquillian, we want to test that this class behaves correctly when invoked as a CDI bean.

4.2. Write Our First Arquillian Test

First, we need to specify that our test class should be run using the framework-specific runner:

@RunWith(Arquillian.class)

If we’re going to run our tests inside a container, we need to use the @Deployment annotation.

Arquillian does not use the entire classpath to isolate the test archive. Instead, it uses the ShrinkWrap class, that is a Java API for creating archives. When we create the archive to test, we specify what files to include in the classpath to use the test. During the deployment, ShrinkWrap isolates only the classes needed for the test.

Using the addclass() method we can specify all necessary classes, and also add an empty manifest resource.

The JavaArchive.class creates a mockup web archive called test.war, this file is deployed into the container and then is used by Arquillian to perform tests:

@Deployment
public static JavaArchive createDeployment() {
    return ShrinkWrap.create(JavaArchive.class)
      .addClass(Component.class)
      .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}

Then we inject our component in the test:

@Inject
private Component component;

Finally, we perform our test:

assertEquals("Message, MESSAGE",component.message(("MESSAGE")));
 
component.sendMessage(System.out, "MESSAGE");

5. Testing Enterprise Java Beans

5.1. Enterprise Java Bean

With Arquillian we can test dependency injection of an Enterprise Java Bean, to do that we create a class that has a method for converting any word to lowercase:

public class ConvertToLowerCase {
    public String convert(String word){
        return word.toLowerCase();
    }
}

Using this class, we create a stateless class for calling the method created before:

@Stateless
public class CapsConvertor {
    public ConvertToLowerCase getLowerCase(){
        return new ConvertToLowerCase();
    }
}

The CapsConvertor class gets injected into a service bean:

@Stateless
public class CapsService {
 
    @Inject
    private CapsConvertor capsConvertor;
    
    public String getConvertedCaps(final String word){
        return capsConvertor.getLowerCase().convert(word);
    }
}

5.2. Test the Enterprise Java Bean

Now we can use Arquillian to test our enterprise Java Bean, injecting the CapsService:

@Inject
private CapsService capsService;
    
@Test
public void givenWord_WhenUppercase_ThenLowercase(){
    assertTrue("capitalize".equals(capsService.getConvertedCaps("CAPITALIZE")));
    assertEquals("capitalize", capsService.getConvertedCaps("CAPITALIZE"));
}

Using ShrinkWrap, we ensure that all classes are wired correctly:

@Deployment
public static JavaArchive createDeployment() {
    return ShrinkWrap.create(JavaArchive.class)
      .addClasses(CapsService.class, CapsConvertor.class, ConvertToLowerCase.class)
      .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}

6. Testing JPA

6.1. Persistence

We can also use Arquillian to test persistence. First, we are going to create our entity:

@Entity
public class Car {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @NotNull
    private String name;

    // getters and setters
}

We have a table that holds names of cars.

Then we are going to create our EJB to perform basic operations on our data:

@Stateless
public class CarEJB {
 
    @PersistenceContext(unitName = "defaultPersistenceUnit")
    private EntityManager em;
 
    public Car saveCar(Car car) {
        em.persist(car);
        return car;
    }
 
    public List<Car> findAllCars() {
    Query query 
      = em.createQuery("SELECT b FROM Car b ORDER BY b.name ASC");
    List<Car> entries = query.getResultList();
    
    return entries == null ? new ArrayList<>() : entries;    
 
    public void deleteCar(Car car) {
        car = em.merge(car);
        em.remove(car);
    }
}

With saveCar we can save the car names into the database, we can get all cars stored with findAllCars, and also we can delete a car from the database with deleteCar.

6.2. Test Persistence With Arquillian

Now we can perform some basic tests using Arquillian.

First, we add our classes to our ShrinkWrap:

.addClasses(Car.class, CarEJB.class)
.addAsResource("META-INF/persistence.xml")

Then we create our test:

@Test
public void testCars() {
    assertTrue(carEJB.findAllCars().isEmpty());
    Car c1 = new Car();
    c1.setName("Impala");
    Car c2 = new Car();
    c2.setName("Lincoln");
    carEJB.saveCar(c1);
    carEJB.saveCar(c2);
 
    assertEquals(2, carEJB.findAllCars().size());
 
    carEJB.deleteCar(c1);
 
    assertEquals(1, carEJB.findAllCars().size());
}

In this test, we first create four car instances, and we check that the number of rows in the database is the same we created.

8. Conclusion

In this tutorial, we:

  • introduced Arquillian core concepts
  • injected a component into the Arquillian test
  • tested an EJB
  • tested persistence
  • performed the Arquillian test using Maven

You can find the code from the article over on Github.


« 上一篇: JUnitParams简介