Functional Testing with Arquillian
Arquillian is not only a great tool for testing inside of the container but it greatly reduces the effort for functional testing. In the following article, I'll show you how to easily use it to test a web UI of the application you're building.
Getting Started
This article exercises following technologies:
- Arquillian, Arquillian documentation
- Arquillian Drone, Arquillian Drone documentation
- ShrinkWrap Maven Resolver
- Selenium
The first three are a part of JBoss testing umbrella, the tests themselves are based on Selenium. If you are already familiar with Selenium, you'll find that Arquillian manages not only a container life cycle but it makes your live easier even w.r.t. Selenium life cycle. If Selenium is a new stuff to you, you might find that it is exactly the missing part in your tests.
Explaining the concepts of in-container and client testing
If you'd ever written an Arquillian based test, you know that it looks almost the same as others tests in the framework you're using. I prefer JUnit here, so let's make an example:
@RunWith(Arquillian.class) public class BasicInContainerTest { @Deployment public static JavaArchive createDeployment() { return ShrinkWrap.create(JavaArchive.class) .addClass(MyBean.class); } @Inject MyBean bean; @Test public void testBeanInstance() { Assert.assertNotNull(bean); .. } }
Here I've deployed a CDI bean on the server, and in the test I'm injecting the instance and asserting it is working correctly. This means that Arquillian enriches the archive, connects to the server itself, running test method inside of the container. This is actually the default behavior. This way, the JUnit test runner communicates with Arquillian, tests are executed in server JVM and results are pushed back. JUnit runner is actually a client here.
The client mode is the second mode of Arquillian testing. This way the tests are executed in the same JVM as the client is running and the testing archive is not enriched. This means you loose the possibility of retrieving objects from server JVM during testing (unless server exposes them otherwise), however Arquillian still controls deployement, undeployment and life cycle of the container.
How to activate client mode?
This is very easy. You can either mark a deployment non-testable on server, meaning Arquillian will not enrich the archive at all, or you can mark a specified method with an annotation @RunAsClient. Want an example? Here you are:
@RunWith(Arquillian.class) public class BasicClientTest { @Deployment(testable=false) public static WebArchive createDeployment() { return ShrinkWrap.create(WebArchive.class) .addClasses(MyBean.class) .setWebXML("WEB-INF/web.xml"); } @Test public void testLogin() { ... } }
Having possibility to annotate a method by @RunAsClient means that Arquillian can run in a mixed mode, giving you the best of both worlds:
... @Deployment public static JavaArchive createDeployment() { return ShrinkWrap.create(JavaArchive.class) .addClass(MyBean.class); } @Inject MyBean bean; @Test public void testBeanInstance() { // this is executed in the server JVM } @Test @RunAsClient public void testFromClient() { // this is executed in the client JVM } ...
Using Arquillian Drone
That was a bit of theory. Let's get back to Arquillian Drone. Arquillian Drone is an extension written in order to manage the life cycle of other objects then servers, to be precise testing browsers.
Currently, the list of supported browsers is pretty large, let's mention SeleniumServer, Selenium, WebDriver and Arquillian Ajocado. As well as Arquillian, Arquillian Drone is pretty extensible, so if your favorite testing browser is not supported, you'll find easy to add the support.
Arquillian Drone bootstraps the tooling required for testing browsers in order to work (e.g. Selenium Server), than it creates instances of testing browser and properly disposes them after the test is finished. You only have to write the logic of the test, not to meddle with the test environment preparation.
Maven configuration
This example is built in Maven and tests are configured against JBoss AS 7.0.1 container. The code can be found on github, at https://github.com/kpiwko/blog/tree/master/drone-selenium. Let's go through Maven dependencies:
<properties> <version.arquillian_core>1.0.0.CR5</version.arquillian_core> <version.arquillian_drone>1.0.0.CR2</version.arquillian_drone> <version.jbossas_7>7.0.1.Final</version.jbossas_7> <version.junit>4.8.2</version.junit> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.arquillian</groupId> <artifactId>arquillian-bom</artifactId> <version>${version.arquillian_core}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.extension</groupId> <artifactId>arquillian-drone-bom</artifactId> <version>${version.arquillian_drone}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
This specifies versions of required tooling, including Selenium. In your dependencies, you'll have to add following artifacts:
<!-- test dependencies --> <dependency> <groupId>org.jboss.arquillian.junit</groupId> <artifactId>arquillian-junit-container</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> <version>${version.junit}</version> </dependency> <dependency> <groupId>org.jboss.arquillian.extension</groupId> <artifactId>arquillian-drone-impl</artifactId> <scope>test</scope> </dependency> <!-- Support for Selenium in Arquillian Drone --> <dependency> <groupId>org.jboss.arquillian.extension</groupId> <artifactId>arquillian-drone-selenium</artifactId> <scope>test</scope> </dependency> <!-- Support for Selenium Server in Arquillian Drone --> <dependency> <groupId>org.jboss.arquillian.extension</groupId> <artifactId>arquillian-drone-selenium-server</artifactId> <scope>test</scope> </dependency> <!-- Selenium --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <scope>test</scope> </dependency> <!-- Selenium Server --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-server</artifactId> <scope>test</scope> </dependency> <!-- Selenium Server dependency, feel free to use different slf4j implementation --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <scope>test</scope> </dependency>
Our example is using ShrinkWrap Maven Resolver to interact with Maven repository from the test itself, so you must include it as well:
<!-- Resolution of JAR libraries into WAR archive --> <dependency> <groupId>org.jboss.shrinkwrap.resolver</groupId> <artifactId>shrinkwrap-resolver-impl-maven</artifactId> <scope>test</scope> </dependency>
Finally, you have to specify the dependency on container. JBoss AS 7.0.1 is extremely fast at startup, so let's Arquillian manage its life cycle:
<dependency> <groupId>org.jboss.as</groupId> <artifactId>jboss-as-arquillian-container-managed</artifactId> <version>${version.jbossas_7}</version> <scope>test</scope> </dependency>
Creating a test deployment
While running in client test mode, you have to make sure you deploy all the required bits. So, you're deployment method might be a bit more complex but there's nothing to worry about. Here is a commented example:
public static final String WEBAPP_SRC = "src/main/webapp"; @Deployment(testable=false) public static WebArchive createLoginScreenDeployment() { MavenDependencyResolver resolver = DependencyResolvers.use(MavenDependencyResolver.class).loadMetadataFromPom("pom.xml"); return ShrinkWrap.create(WebArchive.class, "cdi-login.war") .addClasses(Credentials.class, LoggedIn.class, Login.class, User.class, UsersProducer.class) .addAsResource("import.sql") .addAsResource("test-persistence.xml", "META-INF/persistence.xml") .addAsWebResource(new File(WEBAPP_SRC, "index.html")) .addAsWebResource(new File(WEBAPP_SRC, "home.xhtml")) .addAsWebResource(new File(WEBAPP_SRC, "users.xhtml")) .addAsWebResource(new File(WEBAPP_SRC, "template.xhtml")) .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml") .addAsWebInfResource(new File(WEBAPP_SRC, "WEB-INF/faces-config.xml")) .addAsLibraries(resolver.artifact("org.jboss.seam.solder:seam-solder").resolveAsFiles()) .setWebXML(new File(WEBAPP_SRC, "WEB-INF/web.xml")); }
Here I'm enumerating all the files to illustrate the idea. However, ShrinkWrap can add a package, directory and so on, so your method will likely be shorter. I'm creating a WAR archive to be deployed named cdi-login.war. I include all classes, import.sql to populate database with data, test persistence descriptor, all the jsf pages, web and CDI bean descriptor.
As I'm using Seam 3 in the example, I'm including all the required libraries as well. Here the resolver is configured using metadata available in the pom.xml file, which defines the project. It collects dependency versions, mirrors, repositories etc. and then you can simply specify groupId and artifactId to get the Seam Solder jar library. ShrinkWrap Maven Resolver will actually search the local Maven repository and if required (and not configured otherwise) remote repositories as well and fetches Seam Solder including all required transitive dependencies. Then the jar files are packaged in WEB-INF/lib directory.
Adding a Selenium browser
As you probably noticed, I added Selenium, Selenium Server and corresponding Drone extensions to dependencies in pom.xml file. It's time to used them in the test:
@RunWith(Arquillian.class) public class BasicClientTest { ... @Drone DefaultSelenium browser; ...
That's it! Specify this as a field in the test and Arquillian Drone extension will create an instance of DefaultSelenium browser before the first client test is run and injects the instance. Here I'm using DefaultSelenium, which requires Selenium Server running. However, because I've included Arquillian Drone Extension for Selenium Server, it will be started as well and the browser connects to it automatically. Do not pollute your tests with unnecessary code, keep them simple!
Oh wait, there one more thing. I'm testing web UI but how do I know the URL of deployed application? Arquillian already has a solution:
@RunWith(Arquillian.class) public class BasicClientTest { ... @ArquillianResource URL deploymentURL; ...
Now even the URL of your deployed archive is injected in the test. You can verify the functionality of the web application:
... private static final String USERNAME_FIELD = "id=loginForm:username"; private static final String PASSWORD_FIELD = "id=loginForm:password"; private static final String LOGIN_BUTTON = "id=loginForm:login"; private static final String LOGGED_IN = "xpath=//li[contains(text(),'Welcome')]"; private static final String TIMEOUT = "15000"; @Test public void testLoginAndLogout() { browser.open(deploymentUrl + "home.jsf"); browser.type(USERNAME_FIELD, "demo"); browser.type(PASSWORD_FIELD, "demo"); browser.click(LOGIN_BUTTON); browser.waitForPageToLoad(TIMEOUT); Assert.assertTrue("User should be logged in!", browser.isElementPresent(LOGGED_IN)); } ...
Easy! You've just tested in the Firefox browser that your application login page works correctly.
If you liked the article, stay tuned. There is a lot more in Arquillian Drone to cover, such as configuration of the Arquillian Drone, running different browsers (Firefox, Chromium, IE, etc.); Arquillian Ajocado (Selenium on steroids with an excellent AJAX support included) or WebDriver; multiple browser instances, handling https requests, mobile devices testing and writing support for your own testing browser.
Arquillian makes integration testing a breeze. Arquillian Drone adds a great support for functional tests.
Together, they make developing tests fun again.