Testing

A Step by Step guide on Implementing CRUD RESTful API tests with Junit5 + REST-assured

2much2learn - A Step by Step guide on Implementing CRUD RESTful API tests with Junit5 + REST-assured
Clone the source code of the article from crud-restful-api-testing-using-rest-assured

Introduction

API testing is a type of software testing that involves testing application programming interfaces (APIs) directly and as part of integration testing to determine if they meet expectations for functionality, reliability, performance, and security. Since APIs lack a GUI, API testing is performed at the message layer.

There are many different tools out there that can assist you in writing these automated tests for APIs. This article will focus on getting our hands dirty with one of the most popular open-source Java library REST-assured.

To have a clear picture on how to implement powerful and maintainable API tests suite, for the purpose of this article we will setup a simple CRUD API application and start writing tests to handle different REST operations and response verifications.

Catalogue Management System Restful APIs

We had a previous article published on Creating CRUD Restful APIs using SpringBoot + Spring Data JPA. Below are the operations the application supports.

HTTP
Method
API NamePathResponse
Status Code
POSTCreate Catalogue Item/201
(Created)
GETGet Catalogue Items/200
(Ok)
GETGet Catalogue Item/{sku}200
(Ok)
PUTUpdate Catalogue Item/{sku}200
(Ok)
DELETEDelete Catalogue Item/{sku}204
(No Content)
POSTUpload Catalog Item Picture/{sku}/image201
(Created)

We will be implementing our API tests for these operations that are exposed via the application to manage items for a Catalogue Management System.

Technology stack for implementing Rest-assured tests for CRUD Restful APIs…

Developing with Java & Maven

Java is one of the world’s most widely used computer language. Java is a simple, general-purpose, object-oriented, interpreted, robust, secure, architecture-neutral, portable, high-performance, multithreaded computer language. It is intended to let application developers write once, run anywhere (WORA), meaning that code that runs on one platform does not need to be recompiled to run on another.

Java technology is both a programming language and a platform. It is a high level, robust, secured and object-oriented programming language. And any hardware or software environment in which a program runs, is known as a platform. Since Java has its own runtime environment (JRE) and API, it is called platform.

Maven is a project management and comprehension tool that provides developers a complete build lifecycle framework. Development team can automate the project’s build infrastructure in almost no time as Maven uses a standard directory layout and a default build lifecycle.

To summarize, Maven simplifies and standardizes the project build process. It handles compilation, distribution, documentation, team collaboration and other tasks seamlessly. Maven increases reusability and takes care of most of the build related tasks

JUnit 5

Junit 5 is a powerful and popular Java testing framework and is the next generation of JUnit. It is composed of many different modules and can be summarized as below:

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform serves as a foundation for launching testing frameworks on the JVM. It also defines the TestEngine API for developing a testing framework that runs on the platform.

JUnit Jupiter is the combination of the new programming model and extension model for writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine for running Jupiter based tests on the platform.

JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform.

JUnit 5 requires Java 8 (or higher) at runtime.

Rest-assured

REST-assured is highly opted when implementing Java based Automated API Tests suite because of its powerful features such as:

  • Making an API call needs lot of boilerplate code for setting up HTTP Connection, sending request and extracting response status & payload for test validation. REST-assured eliminates us to write this repeated code.

  • Easy to integrate with existing Java Unit Test libraries such as JUnit5 and TestNG.

  • And lastly, making the tests human readable by implementing the tests with Given-When-Then BDD scenarios.

Hamcrest

Hamcrest is a framework for writing matcher objects allowing match rules to be defined declaratively. Hamcrest is commonly used with junit and TestNG making assertions. Single assertThat statement with appropriate matchers will replace many of JUnit and TestNG assert methods.

Hamcrest comes with a library of useful matchers. Here are some of the most important ones.

MatcherDescription
allOfmatches if all matchers match (short circuits)
anyOfmatches if any matchers match (short circuits)
notmatches if the wrapped matcher doesn’t match and vice
equalTotest object equality using the equals method
isdecorator for equalTo to improve readability
hasToStringtest Object.toString
instanceOf, isCompatibleTypetest type
notNullValue, nullValuetest for null
sameInstancetest object identity
hasEntry, hasKey, hasValuetest a map contains an entry, key or value
hasItem, hasItemstest a collection contains elements
hasItemInArraytest an array contains an element
closeTotest floating point values are close to a given value
greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTotest ordering
equalToIgnoringCasetest string equality ignoring case
equalToIgnoringWhiteSpacetest string equality ignoring differences in runs of whitespace
containsString, endsWith, startsWithtest string matching

For the complete list of matchers, follow API reference.

Below is usage of Hamcrest for few matches:

Hamcrest matchers demonstration
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

// Using instanceOf and shortcut for instanceOf
assertThat(Long.valueOf(1), instanceOf(Integer.class));
assertThat(Long.valueOf(1), isA(Integer.class));

// Verifying Lists for size, contains, anyOrder, greaterThan
List<Integer> list = Arrays.asList(4, 2, 3);

assertThat(list, hasSize(3));
assertThat(list, contains(4, 2, 3)); // Verify if order is in exact order
assertThat(list, containsInAnyOrder(3, 4, 2)); // Verify in any order
assertThat(list, everyItem(greaterThan(1))); // verify if every element is greater than 1

// Using with REST-assured
given()
    .get("/")
.then()
    .assertThat().spec(prepareResponseSpec(200))
.and()
    .assertThat().body("data", is(not(empty())));

Project Lombok

We will be heavily relying on Project Lombok, which is a Java Library which makes our life happier and more productive by helping us to never write another getter or equals method again, constructors which are so repetitive. The way Lombok works is by plugging into our build process and autogenerating Java bytecode into our .class files as per a number of project annotations we introduce in our code.

Below is sample Lombok code for a POJO class:

@Data
@AllArgsConstructor
@RequiredArgsConstructor(staticName = "of")
public class CatalogueItem {

    private Long id;
    private String sku;
    private String name;
}
  • @Data is a convenient shortcut annotation that bundles the features of @ToString, @EqualsAndHashCode, @Getter / @Setter and @RequiredArgsConstructor all together.
  • @AllArgsConstructor generates a constructor with 1 parameter for each field in your class. Fields marked with @NonNull result in null checks on those parameters.
  • @RequiredArgsConstructor generates a constructor with 1 parameter for each field that requires special handling.

IDE Plugins

Install Lombok plugin in IntelliJ Idea or Eclipse to start using the awesome features it provides.

Why use OpenJDK?

Oracle has announced that the Oracle JDK 8 builds released after Jan 2019 cease to be free for commercial use.

An alternative is to use OpenJDK and effort is underway to make them fully interchangeable. A number of companies who are currently using Oracle JDK in production are making the decision to switch to OpenJDK or have already done so.

Why Red Hat’s Build of OpenJDK?

  • Little to No Code Changes - OracleJDK and Red Hat’s implementation of OpenJDK are functionally very similar and should require little to no changes.
  • Java Compliance - Red Hat OpenJDK is baselined from the OpenJDK project and TCK compliant.
  • Multi-Platform Support - Red Hat OpenJDK is optimized for containers and supported on Windows and Linux.

Prepare Development Environment

At bare minimum we need to have JDK installed, maven configured and setup IDE of your choice to start implementing tests.

Install Redhat OpenJDK

OpenJDK 8 for Windows can be installed manually using a ZIP bundle or through a graphical user interface using an MSI-based installer.

Download the MSI-based installer of OpenJDK 8 for Windows for your architecture. Run the installer and follow the on-screen instructions to install OpenJDK 11 for Windows and the desired extra components.

The %JAVA_HOME% environment variable must also be set to use some developer tools. Set the %JAVA_HOME% environment variable as follows:

  • Open Command Prompt as an administrator.

  • Set the value of the environment variable to your OpenJDK 11 for Windows installation path:

~:\> setx /m JAVA_HOME "C:\Progra~1\RedHat\java-11-openjdk-11.0.7.10-1"

Note: If the path contains spaces, use the shortened path name.
  • Restart Command Prompt to reload the environment variables.

Configure Maven

Follow the steps provided here to configure Maven on your machine ad validate if it is configured correctly.

~:\> mvn -v

Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: E:\binaries\apache-maven-3.6.3\bin\..
Java version: 11.0.6, vendor: Oracle Corporation, runtime: C:\Program Files\RedHat\java-11-openjdk-11.0.7.10-1\jre
Default locale: en_AU, platform encoding: Cp1252
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

Execute the below command to create base project which can be used to implement our tests. Provide details when prompted.

~:\> mvn archetype:generate -DarchetypeGroupId=org.toomuch2learn -DarchetypeArtifactId=rest-assured-crud-api-tests -DarchetypeVersion=1.4

Getting Started

Ensure development environment is configured to start proceeding with implementing API Tests for the Catalogue Management Service CRUD Application.

Clone and Start Catalogue Management Service CRUD Restful API application

Clone or download Github Repo to your local machine and navigate to the checked out folder.

~:\> git clone https://github.com/2much2learn/article-feb20-crud-rest-api-using-spring-boot-spring-data-jpa

~:\> cd article-feb20-crud-rest-api-using-spring-boot-spring-data-jpa

Run the below maven command to start the application.

~:\> mvn clean spring-boot:run

By default port 8080 will be used. If need to change the port which should be used, pass additional argument to maven command.

~:\> mvn clean spring-boot:run -Drun.arguments="--server.port=9000"

Or, we can package the application to Jar and start the jar by running the below command

~:\> mvn clean package

~:\> java -jar target/catalogue-crud-0.0.1-SNAPSHOT.jar

~:\> java -jar -Dserver.port=9000 target/catalogue-crud-0.0.1-SNAPSHOT.jar

Replace Maven pom.xml

With all the supporting dependencies, maven build file looks something like below. Replace pom.xml generated in the base project with this.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.toomuch2learn</groupId>
  <artifactId>rest-assured-crud-api-tests</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>rest-assured-crud-api-tests</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <junit.jupiter.version>5.6.0</junit.jupiter.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.12</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.2</version>
    </dependency>

    <dependency>
      <groupId>org.hamcrest</groupId>
      <artifactId>hamcrest</artifactId>
      <version>2.2</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <version>3.0.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
      </plugin>
      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.22.2</version>
      </plugin>
    </plugins>
  </build>

  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
    </repository>
    <repository>
      <id>spring-snapshots</id>
      <name>Spring Snapshots</name>
      <url>https://repo.spring.io/snapshot</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>

</project>

Understanding Rest-assured Syntax

REST-assured provides a fluent API in Given/When/Then syntax based on behavior-driven development (BDD). This results the test to be human readable & easy to understand and takes care of all operations in a single step as below.

Access Get endpoint and validate response
given()
    .pathParam("sku", catalogueItem.getSku())
.when()
    .get("/{sku}")
.then()
    .assertThat().spec(prepareResponseSpec(200))
.and()
    .assertThat().body("name", equalTo(catalogueItem.getName()))
.and()
    .assertThat().body("category", equalTo(catalogueItem.getCategory()));

Defining Test and Supporting Class

Create the package structure and classes as below which are the building blocks for this test suite.

Package Structure
Package Structure

With Jackson library in classpath, REST-assured can serialize and de-serialize objects when handling API request and response. Jackson dependency is already part of the pom.xml that is defined above.

And hence, we included the same model classes that are defined in crud-springboot-data-jpa project. This will ease creating the request body and handling the response content.

  • CatalogueItem.java
  • Category.java
  • Error.java

Test class RestAssuredCatalogueCRUDTest is defined to handle all tests against the RESTful APIs. It imports classes from JUnit5, REST-assured and Hamcrest to setup, access and validate APIs.

Structure of test class with different test methods

Below are the tests that we are planning to include as part of this Test class:

  • Application Health Check
  • Create Catalogue Item
  • Get Catalogue Items
  • Get Catalogue Item by SKU
  • Update Catalogue Item by SKU
  • Delete Catalogue Item by SKU
  • Resource Not Found
  • Handler Not Found
  • Handling Validation Errors
  • Handling Invalid Request

Creating Reusable methods

As each of this is an individual test, the context of complete test execution should be wrapped within its own method. To test Update Catalogue Item, we need to create, update and then get the catalogue item to verify if item is actually updated or not.

This can lead to lot of code duplication as most of the tests would have to include creating the catalogue item before testing its context of execution. For code reusability, we need to create functional methods which can be called from the test methods and should not impact other tests.

Below is such reusable method which handles creating catalogue item request and returning the response.

RestAssuredCatalogueCRUDTest.java
private Response postCreateCatalogueItem(CatalogueItem catalogueItem) throws Exception {
  RequestSpecification request
    = given()
      .contentType("application/json")
      .body(catalogueItem);

  return request.post("/");
}

We should consider creating new instance of CatalogueItem created with unique values, else there will be unique constraint exceptions occurring in the application when persisting the catalogue items to the database. Below reusable methods are created to create Catalogue Item with fields assigned with distinct values.

RestAssuredCatalogueCRUDTest.java

// Create Catalogue Item
CatalogueItem catalogueItem = prepareCatalogueItem(prepareRandomSKUNumber());

final Random random = new Random();
private String prepareRandomSKUNumber() {
  return "SKUNUMBER-"+
    random.ints(1000, 9999)
      .findFirst()
      .getAsInt();
}

private CatalogueItem prepareCatalogueItem(String skuNumber) {
  CatalogueItem item
    = CatalogueItem.of(
      skuNumber,
      "Catalog Item -"+skuNumber,
      "Catalog Desc - "+skuNumber,
      Category.BOOKS.getValue(),
      10.00,
      10,
      new Date()
    );
  return item;
}

As observed, we created prepareRandomSKUNumber method to generate unique SKU number which will be passed to prepareCatalogueItem to create instance of Catalogue Item with random SKU number. This will ensure unique constraint fields are kept unique when executing tests.

And finally, we will be creating one more reusable method to create instance of ResponseSpecification based on the expected response HTTP Status code. This method will be used in all test classes to verify if the response received is with the expected response HTTP Status code.

RestAssuredCatalogueCRUDTest.java
private ResponseSpecification prepareResponseSpec(int responseStatus) {
    return new ResponseSpecBuilder()
        .expectStatusCode(responseStatus)
        .build();
}

Completing the test class

With the common reusable code separated, implementing test methods will be easy by following the Given\When\Then syntax.

As all the tests are performed on the single API endpoints, they all share the same API Base URI. REST-assured provides a convenient way to configure this base uri to be used by all the tests.

RestAssuredCatalogueCRUDTest.java
static {
  RestAssured.baseURI = "http://localhost:8080/api/v1";
}

Application Health Check

Access /actuator/health endpoint and verify if response is successful with HTTP status code 200 and response has got body containing status field as UP.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test if Application is up by accessing health endpoint")
public void test_applicationIsUp() {
    try {
        given()
            .get("http://localhost:8080/actuator/health")
        .then()
            .assertThat().spec(prepareResponseSpec(200))
        .and()
            .assertThat().body("status", equalTo("UP"));
    }
    catch(Exception e) {
        fail("Error occurred while tesing application health check", e);
    }
}

Create Catalogue Item

Create Instance of Catalogue Item with random SKU Number and pass this to postCreateCatalogueItem to post request to create new Catalogue Item. Verify if the request is handled properly by verifying response HTTP status code is 201 and response body containing id field with value grater than 0.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test Create Catalogue Item")
public void test_createCatalogueItem() {
    try {
        postCreateCatalogueItem(prepareCatalogueItem(prepareRandomSKUNumber()))
        .then()
            .assertThat().spec(prepareResponseSpec(201))
        .and()
            .assertThat().body("id", greaterThan(0));
    }
    catch(Exception e) {
        fail("Error occurred while testing catalogue item create endpoint", e);
    }
}

Get Catalogue Items

For the context of this test execution, send two sequential requests for creating catalogue items. Upon accessing Get Catalogue Items, response status should be 200 and the response body should contain list of catalogue items assigned to data field is not empty.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test Get Catalogue Items")
public void test_getCatalogueItems() {
    try {
        postCreateCatalogueItem(prepareCatalogueItem(prepareRandomSKUNumber()));
        postCreateCatalogueItem(prepareCatalogueItem(prepareRandomSKUNumber()));

        given()
            .get("/")
        .then()
            .assertThat().spec(prepareResponseSpec(200))
        .and()
            .assertThat().body("data", is(not(empty())));
    }
    catch (Exception e) {
        fail("Error occurred while testing fetch catalogue items", e);
    }
}

Get Catalogue Item by SKU

Post create catalogue item request and verify if we are receiving the same catalogue item when get request sent with the same sku number in pathParam. Validate the response status is 200 and response body name and category fields match with those that are sent for create request.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test Get Catalogue Item")
public void test_getCatalogueItem() {
    try {
        // Create Catalogue Item
        CatalogueItem catalogueItem = prepareCatalogueItem(prepareRandomSKUNumber());
        postCreateCatalogueItem(catalogueItem);

        // Get Catalogue item with the sku of the catalogue item that is created and compare the response fields
        given()
            .pathParam("sku", catalogueItem.getSku())
        .when()
            .get("/{sku}")
        .then()
            .assertThat().spec(prepareResponseSpec(200))
        .and()
            .assertThat().body("name", equalTo(catalogueItem.getName()))
        .and()
            .assertThat().body("category", equalTo(catalogueItem.getCategory()));
    }
    catch(Exception e) {
        fail("Error occurred while testing fetch catalogue item", e);
    }
}

Update Catalogue Item by SKU

Multiple operations are handled for update request. Create instance of CatalogueItem and send request to create it. Update few fields in the CatalogueItem and pass it to update it by its sku number. Now, access get request passing sku and validating if we are receiving the response body with updated field values.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test Update Catalogue Item")
public void test_updateCatalogueItem() {
    try {
        // Create Catalogue Item
        CatalogueItem catalogueItem = prepareCatalogueItem(prepareRandomSKUNumber());
        postCreateCatalogueItem(catalogueItem);

        // Update catalogue item
        catalogueItem.setName("Updated-"+catalogueItem.getName());
        catalogueItem.setDescription("Updated-"+catalogueItem.getDescription());

        given()
            .contentType("application/json")
            .body(catalogueItem)
            .pathParam("sku", catalogueItem.getSku())
        .when()
            .put("/{sku}")
        .then()
            .assertThat().spec(prepareResponseSpec(200));

        // Get updated catalogue item with the sku of the catalogue item that is created and compare the response fields
        given()
            .pathParam("sku", catalogueItem.getSku())
        .when()
            .get("/{sku}")
        .then()
            .assertThat().spec(prepareResponseSpec(200))
        .and()
            .assertThat().body("name", equalTo(catalogueItem.getName()))
        .and()
            .assertThat().body("category", equalTo(catalogueItem.getCategory()));
    }
    catch(Exception e) {
        fail("Error occurred while testing catalogue item update", e);
    }
}

Delete Catalogue Item by SKU

Similar to Update request, to test Delete request follow the sequence to Create -> Delete -> Get and verify if Get request is returning back with HTTP status code 404 which is Resource Not Found.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test Delete Catalogue Item")
public void test_deleteCatalogueItem() {
    try {
        // Create Catalogue Item
        CatalogueItem catalogueItem = prepareCatalogueItem(prepareRandomSKUNumber());
        postCreateCatalogueItem(catalogueItem);

        // Delete Catalogue Item
        given()
            .pathParam("sku", catalogueItem.getSku())
        .when()
            .delete("/{sku}")
        .then()
            .assertThat().spec(prepareResponseSpec(204));

        // Trying to get the deleted catalogue item should throw 400
        given()
            .pathParam("sku", catalogueItem.getSku())
        .when()
            .get("/{sku}")
        .then()
            .assertThat().spec(prepareResponseSpec(404));
    }
    catch(Exception e) {
        fail("Error occurred while testing catalogue item update", e);
    }
}

Resource Not Found

Accessing Get request with random SKU which is not available in the application should throw HTTP status 404.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test Resource not found")
public void test_resourceNotFound() {
    try {
        given()
            .pathParam("sku", prepareRandomSKUNumber())
            .get("/{sku}")
        .then()
            .assertThat().spec(prepareResponseSpec(404));
    }
    catch(Exception e) {
        fail("Error occurred while testing resource not found", e);
    }
}

Handler Not Found

Accessing API with some invalid uri paths should throw HTTP status 404.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test Handler not found")
public void test_handlerNotFound() {
    try {
        given()
            .get("/invalid/handler")
        .then()
            .assertThat().spec(prepareResponseSpec(404));
    }
    catch(Exception e) {
        fail("Error occurred while testing handler not found", e);
    }
}

Handling Validation Errors

Accessing Create or Update requests with request body containing invalid field values should throw response with HTTP status code 400 and response body containing list of errors which is not empty and the error matching the expected description.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test validation error")
public void test_validationErrors() {
    try {
        CatalogueItem catalogueItem = prepareCatalogueItem(prepareRandomSKUNumber());
        catalogueItem.setCategory("INVALID");

        Response response
            = postCreateCatalogueItem(catalogueItem)
            .then()
                .assertThat().spec(prepareResponseSpec(400))
            .and()
                .extract().response();

        List<Error> errors = Arrays.asList(response.getBody().jsonPath().getObject("errors", Error[].class));

        assertTrue(errors != null && errors.size() > 0);

        assertTrue(errors.get(0).getDescription().equalsIgnoreCase("Invalid category provided"));
    }
    catch(Exception e) {
        fail("Error occurred while testing validation errors", e);
    }
}

Handling Invalid Request

To test Invalid Request, we need to set invalid data to fields which is not acceptable. We cannot create instance of CatalogueItem with unacceptable values apart from what is expected, else compilation issues will occur.

To test such scenario, instead of using model class we are creating instance of Jackson’s JsonNode and constructing the request body structure with one of the field values to invalid datatypes. In this case, it is price field which is double but setting value to string.

This should fail both Create and Update requests with response HTTP status code as 400 and response body containing list of errors and containing expected message Invalid Request.

RestAssuredCatalogueCRUDTest.java
@Test
@DisplayName("Test Invalid Request")
public void test_invalidRequest() {
    try {
        // Create Catalogue Item via JsonObject
        ObjectMapper mapper = new ObjectMapper();
        JsonNode rootNode = mapper.createObjectNode();
        ((ObjectNode) rootNode).put("name", "INVALID");
        ((ObjectNode) rootNode).put("sku", prepareRandomSKUNumber());
        ((ObjectNode) rootNode).put("price", "INVALID");

        String catalogueItem = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);

        Response response =
            given()
                .contentType("application/json")
                .body(catalogueItem)
                .post("/")
            .then()
                .assertThat().spec(prepareResponseSpec(400))
            .and()
                .extract().response();

        List<Error> errors = Arrays.asList(response.getBody().jsonPath().getObject("errors", Error[].class));

        assertTrue(errors != null && errors.size() > 0);

        assertTrue(errors.get(0).getMessage().equalsIgnoreCase("Invalid Request"));
    }
    catch(Exception e) {
        fail("Error occurred while testing invalid request", e);
    }
}

Execution Results

Before executing the tests, Ensure CRUD Application is started and the base uri is using the port on which the Application is running.

Executing from IntelliJ Idea

To run the tests from IntelliJ Idea, run the test class by right-click and choosing Run RestAssuredCatalogueCRUDTest.

IntelliJ Run test class
IntelliJ Run test class

This should result all the tests to be successfully as below:

IntelliJ Run test class results
IntelliJ Run test class results

If CRUD API application is started successfully and no changes done to test project, then there would not be a chance for the tests to fail. But if there is any test method failure, investigate the exception and fix it accordingly.

Executing from Maven Build

Run the below command to execute the tests by maven

~:\> mvn clean test

If tests are successful, something like this should be displayed.

[INFO] Compiling 4 source files to E:\Projects\2much2learn\2much2learn_examples\testing\rest-assured-crud-api-tests\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ rest-assured-crud-api-tests ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.toomuch2learn.crud.catalogue.RestAssuredCatalogueCRUDTest
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.373 s - in org.toomuch2learn.crud.catalogue.RestAssuredCatalogueCRUDTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  9.681 s
[INFO] Finished at: 2020-02-22T21:30:04+11:00
[INFO] ------------------------------------------------------------------------

Conclusion

Apart from what we went through in this article, REST-assured offers many more useful features that can accommodate creating tests suite for different usecases. REST-assured GettingStarted document provides many more options on integrating with various tools and frameworks. Refer to the extensive Usage documentation for additional configurations and advanced usages.

Clone the source code of the article from crud-restful-api-testing-using-rest-assured
author

Madan Narra21 Posts

Software developer, Consultant & Architect

Madan is a software developer, writer, and ex-failed-startup co-founder. He has over 10+ years of experience building scalable and distributed systems using Java, JavaScript, Node.js. He writes about software design and architecture best practices with Java and is especially passionate about Microservices, API Development, Distributed Applications and Frontend Technologies.

  • Github
  • Linkedin
  • Facebook
  • Twitter
  • Instagram

Contents

Related Posts

Get The Best Of All Hands Delivered To Your Inbox

Subscribe to our newsletter and stay updated.