[JAVA] Automatic API testing with Selenium + REST-Assured

When developing a Web API, you can use various UnitTest tools to automate the test if you are testing the API alone, but it is a bit of a problem if you need to interact with the website. One example is when writing API processing that requires authentication such as OAuth. In cases such as those built with SPA etc. that do not work as intended unless displayed on a browser, it is absolutely necessary to access via a browser.

This time, I tried to automate the test using ** Selenium ** for Web automation test and ** REST-Assured ** for API test, so I will summarize it briefly.

Tools Selenium https://github.com/SeleniumHQ/selenium Needless to say, it's a well-known Web Test tool. There is also a Java API, so you can work directly from Java.

REST-Assured https://github.com/rest-assured/rest-assured A library that allows you to write REST API tests in a Method Chain style.

Below is a sample from the README.

given().
    param("key1", "value1").
    param("key2", "value2").
when().
    post("/somewhere").
then().
    body(containsString("OK"));

It is written that the parameter is passed by ** given () **, the API to be called by ** when () ** is specified, and the expected condition is described by ** then () **.

Sample Let's write an API test that assumes web login in the following scenario.

--Enter your ID and Password on the login page to authenticate. --As a result of authentication, Session Cookie is issued. --Make a request with Session Cookie to API.

chromedriver To run Web Test on Selenium, you need a browser and a corresponding driver.

Since we are using Chrome as the browser here, the driver will also download the chromedriver. You can download the driver for each OS from the following site.

https://chromedriver.chromium.org/downloads

Please note that the chromedriver you use must match the version of your Chrome browser. (If the version of Chrome is 81. *, the chromedriver must also be the same version)

build.gradle Set selenium and rest-assured. In the version used this time, guava set as dependencies of selenium seems to be old, so I excluded it by hand and used any version of guava.

ref. https://stackoverflow.com/questions/50267144/chrome-options-selenium-3-10-nosuchmethoderror-com-google-common-collect-im

            // selenium
            dependencySet(group: 'org.seleniumhq.selenium', version: "3.141.59") {
                entry ('selenium-java') {
                    exclude group: "com.google.guava", name: "guava"
                }
                entry ('selenium-support') {
                    exclude group: "com.google.guava", name: "guava"
                }
                entry ('selenium-chrome-driver'){
                    exclude group: "com.google.guava", name: "guava"
                }
            }
            dependency "com.google.guava:guava:29.0-jre"

            // rest-assured
            dependency "com.jayway.restassured:rest-assured:2.9.0"

Tset class Assuming the following environment, let's write an automated test from login to API call. Domains etc. are temporary and do not actually exist.

--Login page: https://wwww.moaikids.com/login --Enter the following information as login information. - username - password --Cookie: Session information is written in a cookie named session.

{
  "status": "ok"
}

SampleTest

package webtest;

import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import static com.jayway.restassured.RestAssured.given;
import static com.jayway.restassured.path.json.JsonPath.from;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class SampleTest {
    private RequestSpecification spec;
    private String session;

    @BeforeEach
    void setup() {
        System.setProperty("webdriver.chrome.driver", "./src/test/resources/chrome/chromedriver");
        ChromeOptions options = new ChromeOptions().addArguments("--headless");
        WebDriver driver = new ChromeDriver(options);

        driver.get("https://wwww.moaikids.com/login");
        driver.findElement(By.name("username")).sendKeys("username");
        driver.findElement(By.name("password")).sendKeys("password");
        WebElement element = driver.findElement(By.className("login"));
        element.submit();
        Cookie cookie = driver.manage().getCookieNamed("session");

        session = cookie.getValue();
        driver.close();

        RequestSpecBuilder build = new RequestSpecBuilder();
        build.setBaseUri("https://api.moaikids.com")
                .addCookie("session", session);
        spec = build.build();
    }

    @Test
    void ok__check() {
        Response response = given().spec(spec).get("/api/check");
        response.then().statusCode(200);
        String json = response.asString();
        System.out.println(json);

        assertNotNull(from(json).get("status"));
        assertEquals(from(json).get("status"), "ok");
    }
}

Specifying the driver

Specify chromedriver for System Property.

System.setProperty("webdriver.chrome.driver", "./src/test/resources/chrome/chromedriver");

selenium option specification

Specify Headless mode for use in testing.

ChromeOptions options = new ChromeOptions().addArguments("--headless");
WebDriver driver = new ChromeDriver(options);

Entering information in the login form

Use the ** sendKeys ** method when entering information in the input form.

driver.get("https://wwww.moaikids.com/login");
driver.findElement(By.name("username")).sendKeys("username");
driver.findElement(By.name("password")).sendKeys("password");
driver.findElement(By.className("login")).submit();

Get cookie information

Get session information from a cookie called session.

Cookie cookie = driver.manage().getCookieNamed("session");
session = cookie.getValue();

Create REST-Assured RequestSpecification

There are many ways to write REST-Assured tests, but it is convenient to create a RequestSpecification if you want to perform common processing for multiple APIs.

The following is an example of always sending a cookie named session when making a request to the API under ** https: /api.moaikids.com **.

RequestSpecBuilder build = new RequestSpecBuilder();
build.setBaseUri("https://api.moaikids.com")
            .addCookie("session", session);
spec = build.build();

API Test Make a request to ** / api / check ** and evaluate the following:

--Status Code 200 is returned by Response. --There is an element called "status" in the JSON of Response Body --The value of "status" is "ok".

    @Test
    void ok__check() {
        Response response = given().spec(spec).get("/api/check");
        response.then().statusCode(200);
        String json = response.asString();
        System.out.println(json);

        assertNotNull(from(json).get("status"));
        assertEquals(from(json).get("status"), "ok");
    }

Running When you run the above test case, Selenium will start and the process will be executed using your browser.

Starting ChromeDriver 81.0.4044.69 (6813546031a4bc83f717a2ef7cd4ac6ec1199132-refs/branch-heads/4044@{#776}) on port 19278
Only local connections are allowed.
Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code.
[1588734871.003][WARNING]: FromSockAddr failed on netmask
May 06, 2020 12:14:31 pm org.openqa.selenium.remote.ProtocolHandshake createSession
information: Detected dialect: W3C
[1588734871.624][SEVERE]: Timed out receiving message from renderer: 0.100
[1588734871.726][SEVERE]: Timed out receiving message from renderer: 0.100

(snip.)

If the conditions specified by then () or assert ** are cleared, the test will be OK, and if the result is different from the intended result, an error will occur. The following is a case where I expected status code 200 but returned 403.

java.lang.AssertionError: 1 expectation failed.
Expected status code <200> doesn't match actual status code <403>.

Instead of summarizing

By combining Selenium and REST-Assured, it was possible to write an automatic test relatively easily even in processing that assumes the use of a browser such as Web Login.

Since a browser is required for execution, it is necessary to devise such as putting a browser in the execution environment when executing with CI etc.

Recommended Posts

Automatic API testing with Selenium + REST-Assured
REST API testing with REST Assured
Testing with com.google.testing.compile
Testing model with RSpec
Test Web API with junit
Use Bulk API with RestHighLevelClient
API creation with Rails + GraphQL
Update gitlab-runner with Ubuntu automatic update
Streamline Java testing with Spock
Link API with Spring + Vue.js
What Java Engineers Choose for Browser "Automatic Testing"? Selenium? Selenide? Geb?