Skip to content

Testing Java Spring applications

Vladimir Turov edited this page Apr 21, 2020 · 25 revisions

In brief

In our Java testing library there is a possibility to test web backend applications based on Spring library. Testing web backend applications written in Python (for example, using Django) is not yet supported.

Spring tests

First of all, this functionality is not available in Library Version 5. At the moment it exists only in the dev branch. To use this functionality you need to replace the current version of the library in gradle dependencies with 'com.github.hyperskill:hs-test:723b24a584891511ffde92a700da585a703ee13e'.

In Spring tests you only need to override generate function, not check because each test will be tested differently. Each test will be assigned its own test function by calling the setCheckFunc method.

Example

Let's test some class named SpringDemoApplication.

First of all, you need to extend SpringTest class and pass this class to the constructor along with the port number on which you are going to run and test this Spring application.

import org.hyperskill.hstest.dev.mocks.web.response.HttpResponse;
import org.hyperskill.hstest.dev.stage.SpringTest;
import org.hyperskill.hstest.dev.testcase.CheckResult;
import org.hyperskill.hstest.dev.testcase.TestCase;
import springdemo.SpringDemoApplication;

import java.util.List;

public class DemoTest extends SpringTest {
    public DemoTest() {
        super(SpringDemoApplication.class, 8889);
    }
}

Second, you need to add a special file with properties to allow shutting down the server externally. It could be done by specifying a resource folder in the user's build.gradle (not in build.gradle used for testing - user's build.gradle is part of the user's solution for the stage).

plugins {
	id 'org.springframework.boot' version '2.1.5.RELEASE'
	id 'java'
}

apply plugin: 'io.spring.dependency-management'

sourceCompatibility = '11'

repositories {
	mavenCentral()
}

sourceSets.main.resources.srcDirs = ["src/resources"]

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	compile("org.springframework.boot:spring-boot-starter-web")
}

And by specifying properties file itself.

server.port=8889
management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true

Let's imagine that on URL /api/user/ the Spring application should return a list of all users in the JSON format (in this case, there should be 2 users in the output). The test will look like this:

new TestCase<>().setCheckFunc((r, a) -> {
    HttpResponse response = get("/api/user/").send();

    if (response.getStatusCode() != 200) {
        return CheckResult.FALSE("GET /api/user/ should respond with " +
            "status code 200, responded: " + response.getStatusCode() + "\n\n" +
            "Response body:\n" + response.getContent());
    }

    JsonElement json;
    try {
        json = response.getJson();
    } catch (Exception ex) {
        return CheckResult.FALSE("GET /api/user/ should return a valid JSON");
    }

    if (!json.isJsonArray()) {
        return CheckResult.FALSE("GET /api/user/ should return an array of objects");
    }

    JsonArray array = json.getAsJsonArray();

    if (array.size() != 2) {
        return CheckResult.FALSE("GET /api/user/ should initially return 2 objects");
    }

    return CheckResult.TRUE;
}),

Notice the method get - you can use it to send GET request to the tested application. You also can use post, put and delete methods - they all declared in the SpringTest class.

You can see an example with the post method below (for example, an application should create a new user using POST /api/user/ and return a total number of users):

private String newUser = "{\n" +
    "    \"id\": 8,\n" +
    "    \"name\": \"NEW-USER\",\n" +
    "    \"email\": \"superuser@hyperskill.org\",\n" +
    "    \"skills\": [\n" +
    "        {\n" +
    "            \"name\": \"JAVA\"\n" +
    "        },\n" +
    "        {\n" +
    "            \"name\": \"KOTLIN\"\n" +
    "        }\n" +
    "    ]\n" +
    "}";

...

new TestCase<>().setCheckFunc((r, a) -> {
    HttpResponse response = post("/api/user/", newUser).send();

    if (response.getStatusCode() != 200) {
        return CheckResult.FALSE("POST /api/user/ should respond with " +
            "status code 200, responded: " + response.getStatusCode() + "\n\n" +
            "Response body:\n" + response.getContent());
    }

    int totalUsers;
    try {
        totalUsers = Integer.parseInt(response.getContent());
    } catch (NumberFormatException ex) {
        return CheckResult.FALSE("POST /api/user/ " +
            "should create a user and return a number of users. No number was in the response.");
    }

    if (totalUsers != 3) {
        return CheckResult.FALSE("POST /api/user/ " +
            "should create a user and return a number of users. " +
            "Expected to receive \"3\", received: \"" + totalUsers + "\"");
    }

    return CheckResult.TRUE;
}),