26.2. Integration testing Seam applications
Integration testing is slightly more difficult. In this case, we can't eliminate the container infrastructure; indeed, that is part of what is being tested! At the same time, we don't want to be forced to deploy our application to an application server to run the automated tests. We need to be able to reproduce just enough of the container infrastructure inside our testing environment to be able to exercise the whole application, without hurting performance too much.
A second problem is emulating user interactions. A third problem is where to put our assertions. Some test frameworks let us test the whole application by reproducing user interactions with the web browser. These frameworks have their place, but they are not appropriate for use at development time.
The approach taken by Seam is to let you write tests that script your components while running inside a pruned down container environment (Seam, together with the JBoss Embeddable EJB container). The role of the test script is basically to reproduce the interaction between the view and the Seam components. In other words, you get to pretend you are the JSF implementation!
This approach tests everything except the view.
Let's consider a JSP view for the component we unit tested above:
<html>
<head>
<title>Register New User</title>
</head>
<body>
<f:view>
<h:form>
<table border="0">
<tr>
<td>Username</td>
<td><h:inputText value="#{user.username}"/></td>
</tr>
<tr>
<td>Real Name</td>
<td><h:inputText value="#{user.name}"/></td>
</tr>
<tr>
<td>Password</td>
<td><h:inputSecret value="#{user.password}"/></td>
</tr>
</table>
<h:messages/>
<h:commandButton type="submit" value="Register" action="#{register.register}"/>
</h:form>
</f:view>
</body>
</html>
We want to test the registration functionality of our application (the stuff that happens when the user clicks the Register button). We'll reproduce the JSF request lifecycle in an automated TestNG test:
public class RegisterTest extends SeamTest
{
@Test
public void testRegister() throws Exception
{
new FacesRequest() {
@Override
protected void processValidations() throws Exception
{
validateValue("#{user.username}", "1ovthafew");
validateValue("#{user.name}", "Gavin King");
validateValue("#{user.password}", "secret");
assert !isValidationFailure();
}
@Override
protected void updateModelValues() throws Exception
{
setValue("#{user.username}", "1ovthafew");
setValue("#{user.name}", "Gavin King");
setValue("#{user.password}", "secret");
}
@Override
protected void invokeApplication()
{
assert invokeMethod("#{register.register}").equals("success");
}
@Override
protected void renderResponse()
{
assert getValue("#{user.username}").equals("1ovthafew");
assert getValue("#{user.name}").equals("Gavin King");
assert getValue("#{user.password}").equals("secret");
}
}.run();
}
...
}
Notice that we've extended SeamTest
, which provides a Seam environment for our components, and written our test script as an anonymous class that extends SeamTest.FacesRequest
, which provides an emulated JSF request lifecycle. (There is also a SeamTest.NonFacesRequest
for testing GET requests.) We've written our code in methods which are named for the various JSF phases, to emulate the calls that JSF would make to our components. Then we've thrown in various assertions.
You'll find plenty of integration tests for the Seam example applications which demonstrate more complex cases. There are instructions for running these tests using Ant, or using the TestNG plugin for eclipse:
26.2.1. Using mocks in integration tests
Occasionally, we need to be able to replace the implementation of some Seam component that depends upon resources which are not available in the integration test environment. For example, suppose we have some Seam component which is a facade to some payment processing system:
@Name("paymentProcessor")
public class PaymentProcessor {
public boolean processPayment(Payment payment) { .... }
}
For integration tests, we can mock out this component as follows:
@Name("paymentProcessor")
@Install(precedence=MOCK)
public class MockPaymentProcessor extends PaymentProcessor {
public void processPayment(Payment payment) {
return true;
}
}
Since the MOCK
precedence is higher than the default precedence of application components, Seam will install the mock implementation whenever it is in the classpath. When deployed into production, the mock implementation is absent, so the real component will be installed.