REST Testing with Arquillian in JBoss

This article will explain how we can automate REST web service testing using Arquillian and JBoss web server.

First, you must create a javaee6 war (non-blank) project from jboss-javaee6 archetype. This should create a project with Member model, service, repository, controller and web service resource. The archetype will also generate a test case for the controller with test data source and persistence file.

In case you don't have the archetype, I'm writing the most important classes: Model
package com.broodcamp.jboss_javaee6_war.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;

@SuppressWarnings("serial")
@Entity
@XmlRootElement
@Table(uniqueConstraints = @UniqueConstraint(columnNames = "email"))
public class Member implements Serializable {

@Id
@GeneratedValue
private Long id;

@NotNull
@Size(min = 1, max = 25)
@Pattern(regexp = "[^0-9]*", message = "Must not contain numbers")
private String name;

@NotNull
@NotEmpty
@Email
private String email;

@NotNull
@Size(min = 10, max = 12)
@Digits(fraction = 0, integer = 12)
@Column(name = "phone_number")
private String phoneNumber;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getPhoneNumber() {
return phoneNumber;
}

public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
Web service resource:
package com.broodcamp.jboss_javaee6_war.rest;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.persistence.NoResultException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.broodcamp.jboss_javaee6_war.data.MemberRepository;
import com.broodcamp.jboss_javaee6_war.model.Member;
import com.broodcamp.jboss_javaee6_war.service.MemberRegistration;

/**
* JAX-RS Example
* <p/>
* This class produces a RESTful service to read/write the contents of the
* members table.
*/
@RequestScoped
public class MemberResourceRESTService implements IMemberResourceRESTService {

@Inject
private Logger log;

@Inject
private Validator validator;

@Inject
private MemberRepository repository;

@Inject
MemberRegistration registration;

@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Member> listAllMembers() {
return repository.findAllOrderedByName();
}

@GET
@Path("/{id:[0-9][0-9]*}")
@Produces(MediaType.APPLICATION_JSON)
public Member lookupMemberById(@PathParam("id") long id) {
Member member = repository.findById(id);
if (member == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return member;
}

/**
* Creates a new member from the values provided. Performs validation, and
* will return a JAX-RS response with either 200 ok, or with a map of
* fields, and related errors.
*/
@Override
public Response createMember(Member member) {

Response.ResponseBuilder builder = null;

try {
// Validates member using bean validation
validateMember(member);

registration.register(member);

// Create an "ok" response
builder = Response.ok();
} catch (ConstraintViolationException ce) {
// Handle bean validation issues
builder = createViolationResponse(ce.getConstraintViolations());
} catch (ValidationException e) {
// Handle the unique constrain violation
Map<String, String> responseObj = new HashMap<String, String>();
responseObj.put("email", "Email taken");
builder = Response.status(Response.Status.CONFLICT).entity(
responseObj);
} catch (Exception e) {
// Handle generic exceptions
Map<String, String> responseObj = new HashMap<String, String>();
responseObj.put("error", e.getMessage());
builder = Response.status(Response.Status.BAD_REQUEST).entity(
responseObj);
}

return builder.build();
}

/**
* <p>
* Validates the given Member variable and throws validation exceptions
* based on the type of error. If the error is standard bean validation
* errors then it will throw a ConstraintValidationException with the set of
* the constraints violated.
* </p>
* <p>
* If the error is caused because an existing member with the same email is
* registered it throws a regular validation exception so that it can be
* interpreted separately.
* </p>
*
* @param member
* Member to be validated
* @throws ConstraintViolationException
* If Bean Validation errors exist
* @throws ValidationException
* If member with the same email already exists
*/
private void validateMember(Member member)
throws ConstraintViolationException, ValidationException {
// Create a bean validator and check for issues.
Set<ConstraintViolation<Member>> violations = validator
.validate(member);

if (!violations.isEmpty()) {
throw new ConstraintViolationException(
new HashSet<ConstraintViolation<?>>(violations));
}

// Check the uniqueness of the email address
if (emailAlreadyExists(member.getEmail())) {
throw new ValidationException("Unique Email Violation");
}
}

/**
* Creates a JAX-RS "Bad Request" response including a map of all violation
* fields, and their message. This can then be used by clients to show
* violations.
*
* @param violations
* A set of violations that needs to be reported
* @return JAX-RS response containing all violations
*/
private Response.ResponseBuilder createViolationResponse(
Set<ConstraintViolation<?>> violations) {
log.fine("Validation completed. violations found: " + violations.size());

Map<String, String> responseObj = new HashMap<String, String>();

for (ConstraintViolation<?> violation : violations) {
responseObj.put(violation.getPropertyPath().toString(),
violation.getMessage());
}

return Response.status(Response.Status.BAD_REQUEST).entity(responseObj);
}

/**
* Checks if a member with the same email address is already registered.
* This is the only way to easily capture the
* "@UniqueConstraint(columnNames = "email")" constraint from the Member
* class.
*
* @param email
* The email to check
* @return True if the email already exists, and false otherwise
*/
public boolean emailAlreadyExists(String email) {
Member member = null;
try {
member = repository.findByEmail(email);
} catch (NoResultException e) {
// ignore
}
return member != null;
}
}
Member resource interface to be use in arquillian testing:
package com.broodcamp.jboss_javaee6_war.rest;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.broodcamp.jboss_javaee6_war.model.Member;

@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
@Path("/members")
public interface IMemberResourceRESTService {

@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/")
public Response createMember(Member member);

}

And finally the test class that creates the archive and deploy it on jboss container:
package com.broodcamp.jboss_javaee6_war.test;

import java.net.URL;

import javax.inject.Inject;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.apache.http.HttpStatus;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.extension.rest.client.ArquillianResteasyResource;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.broodcamp.jboss_javaee6_war.model.Member;
import com.broodcamp.jboss_javaee6_war.rest.IMemberResourceRESTService;
import com.broodcamp.jboss_javaee6_war.rest.JaxRsActivator;
import com.broodcamp.jboss_javaee6_war.service.MemberRegistration;

@RunWith(Arquillian.class)
public class MemberResourceRESTServiceTest {

private Logger log = LoggerFactory
.getLogger(MemberResourceRESTServiceTest.class);

@ArquillianResource
private URL deploymentURL;

@Deployment(testable = false)
public static Archive createTestArchive() {
return ShrinkWrap
.create(WebArchive.class, "rest.war")
.addClass(JaxRsActivator.class)
.addPackages(true, "com/broodcamp/jboss_javaee6_war")
.addAsResource("META-INF/test-persistence.xml",
"META-INF/persistence.xml")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
// Deploy our test datasource
.addAsWebInfResource("test-ds.xml");
}

@Inject
MemberRegistration memberRegistration;

@Test
public void testCreateMember(
@ArquillianResteasyResource IMemberResourceRESTService memberResourceRESTService)
throws Exception {
Member newMember = new Member();
newMember.setName("czetsuya");
newMember.setEmail("czetsuya@gmail.com");
newMember.setPhoneNumber("1234567890");

Response response = memberResourceRESTService.createMember(newMember);

log.info("Response=" + response.getStatus());

Assert.assertEquals(response.getStatus(), HttpStatus.SC_OK);
}

}

To run that we need the following dependencies:
For arquillian testing:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet</artifactId>
<scope>test</scope>
</dependency>
For rest testing:
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-rest-client-impl-3x</artifactId>
<version>1.0.0.Alpha3</version>
</dependency>

<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>${version.resteasy}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-rest-client-impl-jersey</artifactId>
<version>1.0.0.Alpha3</version>
<scope>test</scope>
</dependency>

To run the test we need to create a maven profile as follows:
<profile>
<!-- Run with: mvn clean test -Parq-jbossas-managed -->
<id>arq-jbossas-managed</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-arquillian-container-managed</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</profile>

arquillian.xml to be save inside the src/test/resources folder.

<?xml version="1.0" encoding="UTF-8"?>
<!-- JBoss, Home of Professional Open Source Copyright 2013, Red Hat, Inc.
and/or its affiliates, and individual contributors by the @authors tag. See
the copyright.txt in the distribution for a full listing of individual contributors.
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License. -->
<arquillian xmlns="http://jboss.org/schema/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

<!-- Uncomment to have test archives exported to the file system for inspection -->
<!-- <engine> -->
<!-- <property name="deploymentExportPath">target/</property> -->
<!-- </engine> -->

<!-- Force the use of the Servlet 3.0 protocol with all containers, as it
is the most mature -->
<defaultProtocol type="Servlet 3.0" />

<!-- Example configuration for a remote WildFly instance -->
<container qualifier="jboss" default="true">
<!-- By default, arquillian will use the JBOSS_HOME environment variable.
Alternatively, the configuration below can be uncommented. -->
<configuration>
<property name="jbossHome">C:\java\jboss\jboss-eap-6.2</property>
<!-- <property name="javaVmArguments">-Xmx512m -XX:MaxPermSize=128m -->
<!-- -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y -->
<!-- </property> -->
</configuration>
</container>

<engine>
<property name="deploymentExportPath">target/deployments</property>
</engine>

</arquillian>
And invoke maven as: mvn clean test -Parq-jbossas-managed

0 nhận xét:

Đăng nhận xét