Introducing REST testing with arquillian
This article will explain how we can automate REST web service testing using Arquillian and JBoss web server. First, you must create a jav...
https://www.czetsuyatech.com/2014/11/wildfly-arquillian-testing.html
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
And finally the test class that creates the archive and deploy it on jboss container:
To run that we need the following dependencies:
For arquillian testing:
To run the test we need to create a maven profile as follows:
arquillian.xml to be save inside the src/test/resources folder.
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
Post a Comment