Developing Juzu portlets – Step 7: Finishing the job properly with unit test
Previous steps:
– Learn how to develop great Juzu portlets for eXo Platform!
– Step 2: viewing and posting secrets
– Step 3: Building a sexy secret wall
– Step 4: adding Likes and Comments for Secret
– Step 5: Saving Secrets in the JCR
– Step 6: Let the whole world create new secrets
The JuZcret application is now finished, but our project is not perfect…
All through the previous blog posts, we have neglected to tell you about Unit Test, and this certainly is not a good practice.
The reason is that we wanted to keep you focused on a specific topic during each step. That’s why it’s only during this last step that we’ll talk about Unit Test in Juzu.
The good news is that Juzu allows you to leverage Selenium easily to simulate a real application while taking advantage of the speed of JUnit.
So it’s time to write Unit Test for our JuZcret portlet. The portlet will be deployed to an embedded portlet container. Selenium WebDriver will help to simulate almost all user interactions with the application, and then Arquillian will help to integrate with JUnit.
Dependencies
We will use:
- JUnit 4
- Arquillian: A testing framework for managing containers and writing integration tests
- ShrinkWrap: Arquillian’s little brother, used for creating Java archives easily
- Selenium WebDriver: A simple API for simulating browser behavior
To make testing easy, Juzu provides Maven dependencies called depchains that contain all needed dependencies for testing an application with the following tools: Juzu, depchain, and Arquillian and Juzu, depchain, Arquillian, and Tomcat7, which should already be in your
file:<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <dependency> <groupId>org.juzu</groupId> <artifactId>juzu-depchain-arquillian</artifactId> <version>1.0.0-cr1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.juzu</groupId> <artifactId>juzu-depchain-arquillian-tomcat7</artifactId> <version>1.0.0-cr1</version> <scope>test</scope> </dependency>
The Juzu core provides some abstract test classes that make it easier for our tests to interact with Arquillian. Let’s add this dependency and plugin:
[...] <dependency> <groupId>org.juzu</groupId> <artifactId>juzu-core</artifactId> <version>1.0.0-cr1</version> <type>test-jar</type> <scope>test</scope> </dependency> [...] <!-- juzu-core test jar need this configuration --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.16</version> <configuration> <systemPropertyVariables> <targetDir>${project.build.directory}</targetDir> <juzu.test.compiler>javac</juzu.test.compiler> <juzu.test.resources.path>${basedir}/src/test/resources</juzu.test.resources.path> <juzu.test.workspace.path> ${project.build.directory}/workspace </juzu.test.workspace.path> </systemPropertyVariables> </configuration> </plugin> [...]
There are xml parsing library conflicts between htmlunit Webdriver and our eXo JCR. We’ll need to add some “exclusions” tags into the
file. Update the :<dependency> <groupId>org.exoplatform.jcr</groupId> <artifactId>exo.jcr.component.ext</artifactId> <version>1.15.x-SNAPSHOT</version> <scope>provided</scope> <exclusions> <exclusion> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> </exclusion> <exclusion> <groupId>org.exoplatform.core</groupId> <artifactId>exo.core.component.document</artifactId> </exclusion> </exclusions> </dependency>
Test configuration and mocks
We need to add a configuration file for Arquillian:
.<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <arquillian xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://jboss.org/schema/arquillian" xsi:schemaLocation="https://jboss.org/schema/arquillian https://jboss.org/schema/arquillian/arquillian_1_0.xsd"> <extension qualifier="webdriver"> <!-- Needed for html unit web driver --> <property name="javascriptEnabled">true</property> </extension> <container qualifier="tomcat" default="true"> <configuration> <property name="bindHttpPort">8080</property> </configuration> </container> </arquillian>
Overwrite service implementation
We want to focus only on testing the JuZcret controller, not the JCR service. It’s better to mock the SecretService JCR implementation. The good news is that we already have an in-memory service implementation (remember step 2). The bad news is that, today, we cannot override the service declared in
in the unit test. For now, we have a workaround, and in the future, we plan to improve the juzu-core unit test support.Create a mock for a secret service and add it to
:package org.juzu.tutorial.services; import javax.inject.Singleton; import java.util.List; import java.util.Set; import org.juzu.tutorial.models.Comment; import org.juzu.tutorial.models.Secret; @Singleton public class SecretServiceJCRImpl implements SecretService { private SecretService delegate; public SecretServiceJCRImpl() { this.delegate = new SecretServiceMemImpl(); } @Override public List<Secret> getSecrets() { return delegate.getSecrets(); } @Override public void addSecret(String message, String imageUrl) { delegate.addSecret(message, imageUrl); } @Override public Comment addComment(String secretId, Comment comment) { return delegate.addComment(secretId, comment); } @Override public Set<String> addLike(String secretId, String userId) { return delegate.addLike(secretId, userId); } }
The classloader of the test will load the SecretServiceJCRImpl service instead of the one in the main source. This mock service delegates all the tasks to our in-memory implementation.
Note: If you are using IntelliJ, you may get a “Duplicate class found in the file” warning because we have two instances of SecretServiceJCRImpl in the same package (one in
and one in ). Just ignore it.We also have
and , which are the eXo JCR service in . We don’t need them for the test.Let’s mock the eXo kernel provider in
:package org.juzu.tutorial; import javax.inject.Provider; import juzu.inject.ProviderFactory; public class MockProviderFactory implements ProviderFactory { @Override public <T> Provider<? extends T> getProvider(final Class<T> implementationType) throws Exception { return new Provider<T>() { @Override public T get() { return null; } }; } }
Note that the provider return null instance is just the mock provider to satisfy the IOC container. We don’t need a JCR service instance in the test.
We also need to register the mock to the service loader by creating
:org.juzu.tutorial.MockProviderFactory
Test cases
We decided to have a dedicated test case for each result of the tutorial steps. We’ll simulate all available user interactions with the JuZcret portlet using Selenium.
Note: There are still two actions that cannot simulated for now: changing the language and the portlet mode. This should be improved in a future version.
We will develop our unit test in the
file in :package org.juzu.tutorial; import juzu.test.AbstractWebTestCase; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.openqa.selenium.WebDriver; public class JuZcretTestCase extends AbstractWebTestCase { @Deployment(testable = false) public static WebArchive createDeployment() { return createPortletDeployment("org.juzu.tutorial"); } @Drone WebDriver driver; }
We use the createPortletDeployment method from the abstract test class of juzu-core, which allows us to deploy our portlet in an embedded portlet container.
WebDriver is injected by Arquillian and help to simulate the user interactions.
Test rendering
After step 1, we have a running portlet that renders
. The unit test should help to make a quick test on the results of the rendering process.import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; [...] @Test public void testRender() throws Exception { driver.get(getPortletURL().toString()); WebElement body = driver.findElement(By.tagName("body")); assertTrue(body.getText().indexOf("JuZcret Portlet") != -1); System.out.println(driver.getPageSource()); } [...]
Our first test case is very simple:
- Make the request, get the html body element, and be sure that it contains the substring “JuZcret Portlet.“
- Print out the whole server response to the console to see the result.
Test adding secret
After step 2, the user can add new secrets. Thanks to Arquillian and WebdDiver, we can easily simulate user input and submit a form in a JUnit test. Let’s add this new test case for adding a secret:
import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; [...] @Test public void testSecret() throws Exception { driver.get(getPortletURL().toString()); WebElement body = driver.findElement(By.tagName("body")); assertFalse(body.getText().contains("test secret text")); // add secret form WebElement shareBtn = driver.findElement(By.cssSelector(".secret-wall-heading a")); driver.get(shareBtn.getAttribute("href")); // input WebElement secretInput = driver.findElement(By.tagName("textarea")); secretInput.sendKeys("test secret text"); // submit WebElement submitBtn = driver.findElement(By.tagName("button")); submitBtn.click(); // wait for redirecting to index page body = new WebDriverWait(driver, 10).until(new ExpectedCondition<WebElement>() { public WebElement apply(WebDriver drv) { return drv.findElement(By.tagName("body")); } }); assertTrue(body.getText().contains("test secret text")); }
- We assert that there is no “test secret text” in the secret list.
- WebDriver provides an API for finding elements in an html page. We find the URL for the add secret page.
- Find the text area and button, fill out the form, and submit it. All is written using the Java API to simulate the actions. This is a fast and clean way to conduct a UI test.
- After submitting the add secret form, the portlet will redirect to the home page; note that it may take some time, so we need to tell WebDriver to wait until we have the response from the server by WebDriverWait
Test assets
We have tested for rendering and user interactions. In step 3, we improved the Look&Feel portlet. We should test whether the portlet is served with the correct assets (CSS and JS files) to make sure that all our declarations for assets in package-info.java are correct:
import java.util.HashSet; import java.util.List; import java.util.Set; [...] @Test public void testAsset() throws Exception { driver.get(getPortletURL().toString()); List<WebElement> scripts = driver.findElements(By.tagName("script")); Set<String> srcScripts = new HashSet<String>(); for (WebElement elem : scripts) { srcScripts.add(elem.getAttribute("src")); } assertTrue(srcScripts.contains("https://localhost:8080/juzu/assets/org/juzu/tutorial/assets/jquery/1.10.2/jquery.js")); assertTrue(srcScripts.contains("https://localhost:8080/juzu/assets/juzu/impl/plugin/ajax/script.js")); assertTrue(srcScripts.contains("https://localhost:8080/juzu/assets/org/juzu/tutorial/assets/javascripts/secret.js")); WebElement style = driver.findElement(By.tagName("link")); assertEquals("https://localhost:8080/juzu/assets/org/juzu/tutorial/assets/styles/juzcret.css", style.getAttribute("href")); }
All necessary assets should be in the server response for rendering JuZcret. This test allows us to check that all are present:
Our portlet needs three JavaScript files:
- js: This file is a juzu-core Ajax script, which provides a jquery plugin to make Ajax requests in our Juzu controller method.
- js: JQuery is used by script.js and our portlet JS.
- js: Our application JS file.
The juzcret.less file should be compiled and served as juzcret.css.
Test Ajax actions
In step 5, we add some user interactions that were done using Ajax. Fortunately, HtmlUnit does a good job of simulating a browser. It can execute JavaScript and even Ajax actions.
Note: Remember that we have enables js in arquillian.xml:
Let’s test the like feature:
import org.openqa.selenium.support.ui.ExpectedConditions; [...] @Test public void testLike() throws Exception { driver.get(getPortletURL().toString()); // like WebElement likeBtn = driver.findElement(By.cssSelector(".btn-like")); likeBtn.click(); // wait By selector = By.cssSelector(".btn-like .numb"); ExpectedCondition<Boolean> condition = ExpectedConditions.textToBePresentInElement(selector, "1"); assertTrue(new WebDriverWait(driver, 10).until(condition)); }
The test is pretty simple:
- Request the index page; click the Like button.
- Don’t forget to wait until we have a server response; the timeout is 10 second.
The last test is the comment feature test case:
@Test public void testComment() throws Exception { driver.get(getPortletURL().toString()); WebElement body = driver.findElement(By.tagName("body")); assertFalse(body.getText().contains("test comment")); // input WebElement commentInput = driver.findElement(By.cssSelector(".secret-add-comment")); commentInput.sendKeys("test comment"); // submit WebElement submitBtn = driver.findElement(By.cssSelector(".btn-comment")); submitBtn.click(); // wait ExpectedCondition<Boolean> condition = ExpectedConditions.textToBePresentInElement(By.cssSelector(".secr-comments-list"), "test comment"); assertTrue(new WebDriverWait(driver, 10).until(condition)); }
- Check that no comment with the substring “test comment” already exists.
- Add a new comment with the message “test comment.”
- Click on the button to submit the new comment.
- Don’t forget to wait until we have a server response; the timeout is 10 seconds.
Now our JuZcret application is complete.
Perform a clean install:
$ mvn clean install
Ensure that all tests have succeeded.
The final source of step 7 is available for download on Github.
This blog post is the last of the series of posts about Juzu.
Apprentice, you can be proud. You are now a true Juzu developer with the capability to develop more and more funny Juzu applications and evangelize Juzu to everyone around you.
If you have any questions, head to the Juzu forum; we would be pleased to help you.
If you want to contribute to Juzu, here is the Github repo. Please don’t hesitate to contact us.
And in case you missed them, here are the previous steps of the tutorial:
– Learn how to develop great Juzu portlets for eXo Platform!
– Step 2: viewing and posting secrets
– Step 3: Building a sexy secret wall
– Step 4: adding Likes and Comments for Secret
– Step 5: Saving Secrets in the JCR
– Step 6: Let the whole world create new secrets
Join the eXo tribe by registering for the community and get updates, tutorials, support, and access to the Platform and add-on downloads!
Make the most out of eXo Platform 4
Register to the next webinar and get a complete overview of what you can do with eXo Platform 4. Reserve your seat now!