JBoss.orgCommunity Documentation
This chapter walks through the details of test execution, covering both the remote and local container cases.
Whilst it's not necessary to understand the details of how Arquillian works, it is often useful to have some insight. This chapter gives you an overview of how Arquillian executes your test for you in your chosen container.
In both JUnit 4 and TestNG 5, a test case is a class which contains at least one test method. The test method
is designated using the @Test
annotation from the respective framework. An Arquillian test
case looks just like a regular JUnit or TestNG test case with two declarative enhancements:
The class contains a static method annotated with @Deployment
that returns a JavaArchive
The class is annotated with @RunWith(Arquillian.class)
(JUnit) or extends
Arquillian
(TestNG)
With those two modifications in place, the test is recognized by the Arquillian test runner and will be executed in the target container. It can also use the extra functionality that Arquillian provides—namely container resource injections and the injection of beans.
When the Arquillian test runner processes a test class, the first thing it does is retrieve the definition of
the Java archive from the @Deployment
method, appends the test class to the archive and
packages the archive using ShrinkWrap.
The name of the archive is irrelevant, so the base name "test" is typically choosen (e.g., test.jar, test.war). Once you have created the shell of the archive, the sky is really the limit of how you can assemble it. You are customizing the layout and contents of the archive to suit the needs of the test. Essentially, you creating a micro application in which to execute the code under test.
You can add the following artifacts to the test archive:
Java classes
A Java package (which adds all the Java classes in the package)
Classpath resources
File system resources
A programmatically-defined file
Java libraries (JAR files)
Other Java archives defined by ShrinkWrap
Consult the ShrinkWrap API to discover all the options you have available for constructing the test archive.
After the Arquillian test runner packages the test archive, it deploys it to the container. For a remote container, this means copying the archive the hot deployment directory or deploying the archive using the container's remote deployment service. In the case of a local container, such as Weld SE, deploying the archive simply means registering the contents of the archive with the runtime environment.
How does Arquillian support multiple containers? And how are both remote and local cases supported? The answer to this question gets into the extensibility of Arquillian.
Arquillian delegates to an SPI (service provider interface) to handle starting and stopping the server and
deploying and undeploying archives. In this case, the SPI is the interface
org.jboss.arquillian.spi.DeployableContainer
. If you recall from the getting started
section, we included an Arquillian library according to the target container we wanted to use. That library
contains an implementation of this interface, thus controlling how Arquillian handles deployment. If you wanted to
introduce support for another container in Arquillian, you would simply provide an implementation of this
interface.
With the archive deployed, all is left is negotiating execution of the test and capturing the results. As you would expect, once all the methods in the test class have be run, the archive is undeployed.
The last operation that Arquillian performs before executing the individual test methods is "enriching" the
test class instance. This means hooking the test class to the container environment by satisfying its injection
points. The enrichment is provided by any implementation of the
org.jboss.arquillian.spi.TestEnricher
SPI on the classpath. Chapter 5, Test enrichment
details the injection points that Arquillian supports.
The question at this point is, how does Arquillian negotiate with the container to execute the test
when the test framework is being invoked locally? Technially the mechanism is pluggable using another SPI,
org.jboss.arquillian.spi.ContainerMethodExecutor
. Arquillian provides a default
implementation for remote servers which uses HTTP communication and an implementation for local tests, which
works through direct execution of the test in the same JVM. Let's have a look at how the remote execution
works.
The archive generator bundles and registers (in the web.xml descriptor) an HttpServlet,
org.jboss.arquillian.protocol.servlet.ServletTestRunner, that responds to test execution GET requests. The test
runner on the client side delegates to the org.jboss.arquillian.spi.ContainerMethodExecutor
SPI implementation, which originates these test execution requests to transfer control to the container JVM.
The name of the test class and the method to be executed are specified in the request query parameters named
className and methodName, respectively.
When the test execution request is received, the servlet delegates to an implementation of the
org.jboss.arquillian.spi.TestRunner
SPI, passing it the name of the test class and the test
method. TestRunner
generates a test suite dynamically from the test class and method name
and runs the suite (now within the context of the container).
The ServletTestRunner
translates the native test result object of JUnit or TestNG into a
org.jboss.arquillian.spi.TestResult
and passes it back to the test executor on the client
side by serializing the translated object into the response. The object gets encoded as either html or a
serialized object, depending on the value of the outputMode request parameter that was passed to the servlet.
Once the result has been transfered to the client-side test runner, the testing framework (JUnit or TestNG)
wraps up the run of the test as though it had been executed in the same JVM.
Now you should have an understanding for how tests can be executed inside the container, but still be executed using existing IDE, Ant and Maven test plugins without any modification. Perhaps you have even started thinking about ways in which you can enhance or extend Arquillian. But there's still one challenge that remains for developing tests with Arquillian. How do you debug test? We'll look at how to hook a debugger into the test execution process in the next chapter.
So far, we've focused on testing your application internals, but we also want to test how others (people, or other programs) interact with the application. Typically, you want to make sure that every use case and execution path is fully tested. Third parties can interact with your application in a number of ways, for example web services, remote EJBs or via http. You need to check that you object serialization or networking work for instance.
This is why Arquillian comes with two run modes, IN_CONTAINER
and AS_CLIENT
.
IN_CONTAINER
is to test your application internals and AS_CLIENT
is to test
how your application is used by clients. Lets dive a bit deeper into the differences between the run modes and
see how they effect your test execution and packaging.
@RunWith(Arquillian.class)
@Run(IN_CONTAINER)
public class MyTestCase
As we mentioned above, we need to repackage your @Deployment
, adding some Arquillian support
classes, to run in-container. This gives us the ability to communicate with the test, enrich the test and run the
test remotely. In this mode, the test executes in the remote container; Arqullian uses this mode by default.
Here is an overview of the expected output of the packaging process when you provide a @Deployment
.
Table 6.1. Servlet Protocol 2.5 rules
@Deployment | Output | Action |
---|---|---|
JavaArchive | EnterpriseArchive | Create a new EnterpriseArchive , add @Deployment and ServletProtocol as module, the other Auxiliary Archives as libraries. |
WebArchive | Exception |
Can not merge two WebArchives and both packaged in a EnterpriseArchive will result in isolation issues.
[a]
|
EnterpriseArchive | EnterpriseArchive | Same as JavaArchive , but using the @Deployment defined EnterpriseArchive instead of creating a new. |
[a] In the current release Arquillian does not try to merge descriptor files, like web.xml, nor can you
avoid it trying to package the |
Table 6.2. Servlet Protocol 3.0 rules
@Deployment | Output | Action |
---|---|---|
JavaArchive | WebArchive | Creates a new WebArchive , adds @Deployment and Auxiliary Archives as libraries. |
WebArchive | WebArchive | Adds @Deployment and Auxiliary Archives as libraries. |
EnterpriseArchive | EnterpriseArchive | Creates a new WebArchive , adds @Deployment and Auxiliary Archives as libraries. |
In Alpha4 Arquillian only has one type of protocol implementation for communicating with remote servers, the servlet protocol.
So the reason for the big difference in packaging between EE 5 and EE 6 containers is mainly due to the support of
Web Fragments in the EE 6 specification. Web Fragments lets Arquillian add it self to the @Deployment
without drastically changing it, but it also means it has to be a WebArchive
in the deployment.
In the next release you will be able to control the packaging your self and we will also look into other protocol implementations like jmx and remote ejb.
@RunWith(Arquillian.class)
@Run(AS_CLIENT)
public class MyTestCase
Now this mode is the easy part. As apposed to in-container mode which repackages and overrides the test
execution, the as-client mode does as little as possible. It does not repackage your @Deployment
nor does it forward the test execution to a remote server. Your test case is running in your JVM as expected
and you're free to test the container from the outside, as your clients see it. The only thing Arquillian
does is to control the lifecycle of your @Deployment
.
Here is an example calling a Servlet using the AS_CLIENT
mode.
@RunWith(Arquillian.class)
@Run(AS_CLIENT)
public class LocalRunServletTestCase
{
@Deployment
public static WebArchive createDeployment()
{
return ShrinkWrap.create("test.war", WebArchive.class)
.addClass(TestServlet.class);
}
@Test
public void shouldBeAbleToCallServlet() throws Exception
{
String body = readAllAndClose(new URL("http://localhost:8080/test/Test").openStream());
Assert.assertEquals(
"Verify that the servlet was deployed and returns the expected result",
"hello",
body);
}
}
The effect of the different run modes depend on the DeployableContainer
used.
Both modes might seem to behave the same in some Embedded containers, but you should avoid
mixing your internal and external tests. One thing is that they should test different
aspects of your application and different usecases, another is that you will miss the
benefits of switching DeployableContainers
and run the same tests suite against
a remote server if you do.