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.client.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.
@Deployment(testable = true)
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.
See the Complete Protocol Reference for an overview of the expected output of the packaging process when you provide a @Deployment
.
@Deployment(testable = false)
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)
public class LocalRunServletTestCase
{
@Deployment(testable = false)
public static WebArchive createDeployment()
{
return ShrinkWrap.create("test.war", WebArchive.class)
.addClass(TestServlet.class);
}
@Test
public void shouldBeAbleToCallServlet(@ArquillianResource(TestServlet.class) URL baseUrl) throws Exception
{
// http://localhost:8080/test/
String body = readAllAndClose(new URL(baseUrl, "/Test").openStream());
Assert.assertEquals(
"Verify that the servlet was deployed and returns the expected result",
"hello",
body);
}
}
@Deployment(testable = true)
public static WebArchive create()
{
}
@Test // runs in container
public void shouldBeAbleToRunOnClientSide() throws Exception
{
}
@Test @RunAsClient // runs as client
public void shouldBeAbleToRunOnClientSide() throws Exception
{
}
It is also possible to mix the two run modes within the same test class. If you have defined the Deployment
to be testable, you can specify the @Test
method to use run mode as client
by using
the @RunAsClient
annotation. This will allow two method within the same test class to run in different modes.
This can be useful if you in a run as client
mode want to execute against a remote endpoint in your application,
for then in the next test method assert on server side state the remote endpoint migh thave created.
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.