Integration testing of APIs in Spring-Boot( Java) with JUnit | For Beginners

Integration testing of APIs in Spring-Boot( Java) with JUnit | For Beginners

Remember that unforgettable feeling of buying a brand-new car? Now imagine taking it for a spin without checking the engine, brakes, or even turning it on. Building software without thorough testing is like that – a recipe for disaster.

This post is in continuation of the last post we shared related to Spring Boot in which we talked about writing Rest APIs in Spring Boot. Please go through this post if you want to learn how to create rest APIs using Spring Boot. In this post, we are going to discuss how we can test our spring boot application from start to end without any mocking.

First, we have to add the test libraries to the pom.xml, so that the Maven can install them for us. Here is the GitHub Repo that contains the code for this tutorial. Here is the code to add testing libraries to pom.xml

...
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
        ...

Using separate config for testing purposes

We don’t want to add the data to the same database as the production one. So, we will start by creating a separate configuration for handling the test cases. Also, it's always a good practice to have separate configurations anyway. Create a new resource file,src/main/resources/application-test.propertiesand add the following data to it.

spring.datasource.url=jdbc:mysql://localhost:3306/springtestguide
spring.datasource.username=root
spring.datasource.password=<password>
spring.jpa.hibernate.ddl-auto=update

Note: We will have to create the database in our local system to make it work.

Now that we have a separate configuration file, we have to export it as an App configuration so that we can use it in the tests itself.

Add the following code to the file, src/main/java/org/example/AppConfigurationTest.java

package org.example;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ComponentScan("org.example")
@Profile("test")
@PropertySource(value = {"classpath:application-test.properties", "classpath:application.properties"})
public class AppConfigurationTest {

}

Writing Controller test for Spring Boot API.

Now since we are going to test the application from end to end, let’s create a controller test. Add the following code tosrc/test/java/org/example/SchoolControllerTest.java

package org.example;

import org.example.school.School;
import org.example.school.SchoolRepository;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import javax.transaction.Transactional;

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Transactional
public class SchoolControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private SchoolRepository repository;

    TestRestTemplate restTemplate = new TestRestTemplate();

    HttpHeaders headers = new HttpHeaders();

    @Test
    public void testGetSchoolData () throws JSONException {
        HttpEntity<String> entity = new HttpEntity<String>(null, headers);

        ResponseEntity<String> response = restTemplate.exchange(
                createURLWithPort("/schools"),
                HttpMethod.GET, entity, String.class);

        School testSchool = new School(1, "First Location", "Mr. Roshan", "California");
        repository.save(testSchool);

        String expected = "[{\"id\":1,\"name\":\"First Location\",\"principal\":\"Mr. Roshan\",\"address\":\"California\"}]";
        JSONAssert.assertEquals(expected, response.getBody(), false);
    }

    private String createURLWithPort(String uri) {
        return "http://localhost:" + port + uri;
    }
}

Let’s go through the file and understand this.

@ActiveProfiles, is used to let the framework know that we are going to use the given profile. As we have already declared the test Profile in the Configuration file, we can use it here directly.

@RunWith and @SpringBootTest is used to start a simple server to carry out the tests using the SpringBootFramework.

Running on a random port

With SpringBootTest.WebEnvironment.RANDOM_PORT we are telling the framework to launch the application on a Random PORT.

Removing any change made to the database after test completion.

@Transactional will help us to clean the database once tests are completed. We don’t want to keep adding stuff to the database and want to clean it after every test run.

Wrapping something with Transaction is a very general programming concept which is present in almost every Database. According to this, if you run any number of SQL commands inside the transaction and if it fails on any step, the whole transaction will be reverted.

This is fairly useful when you are running related commands, for example, reducing the inventory size and adding stuff to someone's brought list.

The same concept is used here, the only hack is that anything you run inside @Transactional is rolled back in the end.

Read more about this on Spring boot logs.

Creating the API endpoint

    @LocalServerPort
    private int port;

    private String createURLWithPort(String uri) {
        return "http://localhost:" + port + uri;
    }

These are used to create the URL for a given endpoint to hit.

Creating the School Object

    @Autowired
    private SchoolRepository repository;
    ...
        School testSchool = new School(1, "First Location", "Mr. Ranvir", "California");
        repository.save(testSchool);
    ...

This code is used to create the object in the database.

Sending the request and getting the response

    TestRestTemplate restTemplate = new TestRestTemplate();

    HttpHeaders headers = new HttpHeaders();
    ...
        HttpEntity<String> entity = new HttpEntity<String>(null, headers);

        ResponseEntity<String> response = restTemplate.exchange(
                createURLWithPort("/schools"),
                HttpMethod.GET, entity, String.class);
    ...

Checking if the response is equal to the object created

JSONAssert.assertEquals(expected, response.getBody(), false);

Here we are checking if the body of the response is equal to the expected response.

Run the tests

You can use the following command to run the tests.

mvn test

In this journey, we've explored end-to-end testing of your Spring Boot API without mocking. By embracing real-world interactions and leveraging powerful tools, you've equipped yourself to build reliable, bug-free APIs that deliver a seamless user experience.

Remember, testing is not a one-time event; it's an ongoing journey. As your API evolves, incorporate testing into your development cycle to ensure ongoing stability and resilience.