Popular Searches
Popular Course Categories
Popular Courses

Implementing Integration Tests for REST APIs

Implementing Integration Tests for REST APIs

Advanced Java Application Testing and Deployment

Implementing Integration Tests for REST APIs

Course: Advance Java Training

Topic: Implementing Integration Tests for REST APIs

Course Link: Advance Java Training – JustAcademy

Demo Link: Register for a Course Demo

Website: JustAcademy Official Website


1. Introduction to REST API Integration Testing

REST APIs allow applications to communicate over HTTP using methods such as GET, POST, PUT, PATCH, and DELETE. In a Spring Boot application, a REST request may pass through a controller, service, repository, database, validation layer, security filters, and exception handlers before a response is returned.

Integration testing verifies that multiple application components work together correctly. Unlike an isolated unit test, which often replaces dependencies with mocks, an integration test exercises real application components and their interactions. Depending on the test scope, it may load the Spring application context, access a test database, or send HTTP requests to a running server.

For example, testing a product API may involve sending a POST request, validating the JSON request body, saving a product to a test database, and checking that the API returns the correct HTTP status and response data.

2. Learning Objectives

  • Understand the difference between unit testing and integration testing.
  • Learn the role of JUnit 5 and Spring Boot Test.
  • Configure REST API integration tests in a Spring Boot project.
  • Use MockMvc to test REST endpoints without starting a real HTTP server.
  • Use a random-port server test when real HTTP communication needs to be verified.
  • Test HTTP methods, response status codes, JSON bodies, and headers.
  • Verify validation, error handling, persistence, and security behavior.
  • Use test databases and test data cleanup strategies.
  • Build a practical integration testing workflow for production-oriented APIs.

3. Unit Testing vs. Integration Testing

Feature Unit Testing Integration Testing
Primary goal Verify a small unit of logic in isolation. Verify that multiple components work together.
Dependencies Often replaced with mocks or stubs. Uses real application components and selected real infrastructure.
Execution speed Usually very fast. Usually slower because more components are involved.
Spring context Often unnecessary. Frequently loaded, depending on test scope.
Database Usually mocked or omitted. May use an isolated test database.
Typical tools JUnit and Mockito. Spring Boot Test, MockMvc, TestRestTemplate, WebTestClient, and test databases.
Example Test a service's price calculation. Send a request to an API and verify the persisted result.

4. Why REST API Integration Tests Are Important

A controller may work in isolation while the complete API still fails because of incorrect dependency injection, JSON serialization, database mappings, validation configuration, security rules, or exception handling.

Integration tests help detect these issues by checking the combined behavior of the application.

Key Benefits

  • Verify that API endpoints are correctly mapped.
  • Confirm that request and response JSON structures are correct.
  • Validate that services and repositories work together.
  • Detect database mapping and transaction problems.
  • Confirm expected HTTP status codes and response headers.
  • Test validation and error responses at the API boundary.
  • Increase confidence before deployment and refactoring.

5. Tools and Technologies

Tool Purpose
JUnit 5 Defines and executes automated tests.
Spring Boot Test Provides Spring Boot application testing support.
Spring Test / MockMvc Exercises Spring MVC endpoints through a simulated servlet environment without starting an HTTP server.
Mockito Mocks selected dependencies when isolation is appropriate.
Jackson Converts Java objects to JSON and JSON to Java objects.
H2 or another test database Provides isolated persistence for integration tests.
TestRestTemplate Sends HTTP requests to a running Spring Boot test server.
WebTestClient Tests WebFlux applications and can also perform HTTP-based tests against a running server.
Maven or Gradle Manages dependencies and runs the test suite.

6. Setting Up a Spring Boot Test Project

Create a Spring Boot project with the web and persistence dependencies required by the application. Add the testing dependencies supported by the project's Spring Boot version.

For a typical Spring Boot project, the test starter may be included through the Spring Boot testing setup. A Maven configuration for a modern Spring MVC testing setup can include the following dependencies; verify the artifact names against your Spring Boot version.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

The web and JPA dependencies belong to the application's runtime setup, while the test and H2 dependencies support testing. Depending on the Spring Boot release, web testing may use more specific test starter artifacts. Use dependency management from the selected Spring Boot parent or BOM instead of independently choosing incompatible versions.

7. Understanding @SpringBootTest

The @SpringBootTest annotation loads a Spring Boot application context for testing. It is useful when the test needs a broad set of application components, such as controllers, services, repositories, configuration, and persistence setup.

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ApplicationIntegrationTest {

    @Test
    void applicationContextLoads() {
        // If the context starts successfully, this test passes.
    }
}

This test verifies that the configured application context can start. It does not, by itself, prove that every REST endpoint behaves correctly.

Web Environment Options

Option Description Typical Use
MOCK Loads a web application context without starting a real embedded server. Testing MVC endpoints with MockMvc.
RANDOM_PORT Starts a real embedded server on an available port. Testing actual HTTP communication with the running application.
DEFINED_PORT Starts the server on the configured port. Special test environments where a fixed port is required.
NONE Loads a non-web application context. Testing application components without a web environment.

8. Creating a Sample REST API

To demonstrate integration testing, create a small product API. The example uses a controller and service so the test can verify the request-to-response flow.

8.1 Product Model

public class Product {
    private Long id;
    private String name;
    private double price;

    public Product() {
    }

    public Product(Long id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    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 double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

8.2 Product Service

import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class ProductService {
    private final Map<Long, Product> products =
            new ConcurrentHashMap<>();
    private final AtomicLong sequence = new AtomicLong();

    public Product create(Product product) {
        Long id = sequence.incrementAndGet();
        Product saved = new Product(
                id, product.getName(), product.getPrice());
        products.put(id, saved);
        return saved;
    }

    public Product findById(Long id) {
        return products.get(id);
    }
}

This in-memory service is suitable for illustrating HTTP and Spring MVC testing, but it is not a database-persistence example. For production-oriented integration testing, replace it with a repository-backed service and test database.

8.3 Product Controller

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/products")
public class ProductController {
    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @PostMapping
    public ResponseEntity<Product> create(
            @RequestBody Product product) {
        Product saved = productService.create(product);
        return ResponseEntity.status(HttpStatus.CREATED).body(saved);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Product> findById(
            @PathVariable Long id) {
        Product product = productService.findById(id);

        if (product == null) {
            return ResponseEntity.notFound().build();
        }

        return ResponseEntity.ok(product);
    }
}

The API provides two endpoints:

  • POST /api/products creates a product and returns HTTP 201 Created.
  • GET /api/products/{id} returns a product with HTTP 200 OK or HTTP 404 Not Found.
```html

9. Implementing REST API Integration Tests with MockMvc

MockMvc allows tests to send simulated HTTP requests through Spring MVC's request-processing infrastructure without starting an actual HTTP server. It can exercise controller mappings, request binding, JSON conversion, validation, exception handling, and configured filters.

For integration-style testing, combine @SpringBootTest with @AutoConfigureMockMvc to load the application context and inject MockMvc.

9.1 Writing the First GET Request Test

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class ProductApiIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldReturnNotFoundForMissingProduct() throws Exception {
        mockMvc.perform(get("/api/products/999"))
                .andExpect(status().isNotFound());
    }
}

The test sends a GET request to the product endpoint. Since the sample service returns null for an unknown ID, the controller should return HTTP 404.

9.2 Testing a POST Request with JSON

Use post() to send a request and contentType() to specify that the body contains JSON. MockMvc can then verify the HTTP response and selected JSON fields.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class ProductCreationIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldCreateProduct() throws Exception {
        String requestBody =
                "{\"name\":\"Keyboard\",\"price\":1500.0}";

        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(requestBody))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").exists())
                .andExpect(jsonPath("$.name").value("Keyboard"))
                .andExpect(jsonPath("$.price").value(1500.0));
    }
}

This test verifies that the API accepts a JSON request, creates a product, and returns the expected response structure. Because the sample service uses in-memory storage, this test does not verify database persistence.

10. Testing HTTP Status Codes

HTTP status codes communicate the outcome of an API request. Integration tests should confirm that endpoints return appropriate status codes for successful operations, missing resources, invalid requests, and server-side failures.

Status Code Meaning Example
200 OK The request succeeded. Retrieving an existing product.
201 Created A resource was created successfully. Creating a product with POST.
204 No Content The request succeeded without a response body. Deleting a resource successfully.
400 Bad Request The request is invalid. Malformed JSON or failed request validation.
401 Unauthorized Authentication is missing or invalid. Calling a protected endpoint without valid authentication.
403 Forbidden The caller lacks required permission. Accessing an administrator-only endpoint as a regular user.
404 Not Found The requested resource or route does not exist. Requesting an unknown product ID.
409 Conflict The request conflicts with the current resource state. Creating a resource with a duplicate unique identifier.
500 Internal Server Error An unexpected server-side error occurred. An unhandled application failure.

Do not return a generic status code for every situation. Define consistent API behavior and test it as part of the application's contract.

11. Validating JSON Response Bodies

A status code alone does not prove that an API response is correct. Use JSON assertions to validate important response fields and their values.

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

mockMvc.perform(get("/api/products/1"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(1))
        .andExpect(jsonPath("$.name").value("Keyboard"))
        .andExpect(jsonPath("$.price").value(1500.0));

These assertions assume that product ID 1 exists and contains the stated values. Tests should create or arrange their own data rather than depend on unspecified state left by another test.

Useful JSON Assertions

Assertion Purpose
jsonPath("$.name").value("Keyboard") Checks a field's value.
jsonPath("$.id").exists() Checks that a field exists.
jsonPath("$.items").isArray() Checks that a field is an array.
jsonPath("$.items").isEmpty() Checks that an array or collection is empty where supported.
jsonPath("$.name").doesNotExist() Checks that a field is absent.

12. Testing Response Headers and Content Types

REST API tests should check important response headers as well as response bodies. For example, a JSON endpoint should normally return an appropriate JSON content type.

import org.springframework.http.MediaType;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;

mockMvc.perform(get("/api/products/1")
        .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(content().contentTypeCompatibleWith(
                MediaType.APPLICATION_JSON));

You can also verify headers such as Location when an API returns a resource URL after creation, or caching headers when they are part of the API contract.

13. Testing the Complete Create-and-Read Workflow

One important integration scenario is creating a resource and then retrieving it through the API. This checks that the response from the first request can be used in the second request.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

@SpringBootTest
@AutoConfigureMockMvc
class ProductWorkflowIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void shouldCreateAndRetrieveProduct() throws Exception {
        String requestJson =
                "{\"name\":\"Mouse\",\"price\":750.0}";

        MvcResult createResult = mockMvc.perform(
                post("/api/products")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(requestJson))
                .andExpect(status().isCreated())
                .andReturn();

        JsonNode responseJson = objectMapper.readTree(
                createResult.getResponse().getContentAsString());

        long productId = responseJson.get("id").asLong();

        mockMvc.perform(get("/api/products/" + productId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(productId))
                .andExpect(jsonPath("$.name").value("Mouse"))
                .andExpect(jsonPath("$.price").value(750.0));
    }
}

This test creates a product, extracts its generated ID, and retrieves it through a second HTTP request. It verifies the API workflow rather than testing the controller method directly.

14. Testing Request Validation

REST APIs should validate incoming data before processing it. Jakarta Bean Validation annotations such as @NotBlank, @Positive, and @NotNull can define request constraints.

14.1 Create a Validated Request DTO

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;

public class CreateProductRequest {

    @NotBlank
    private String name;

    @Positive
    private double price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

14.2 Add Validation to the Controller

import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/validated-products")
public class ValidatedProductController {

    @PostMapping
    public ResponseEntity<CreateProductRequest> create(
            @Valid @RequestBody CreateProductRequest request) {

        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(request);
    }
}

Ensure the application includes a compatible Bean Validation implementation, such as the Spring Boot validation starter. The exact dependencies depend on the Spring Boot version and project configuration.

14.3 Test Invalid Input

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class ProductValidationIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldRejectBlankProductName() throws Exception {
        String requestJson =
                "{\"name\":\" \",\"price\":500.0}";

        mockMvc.perform(post("/api/validated-products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(requestJson))
                .andExpect(status().isBadRequest());
    }

    @Test
    void shouldRejectNonPositivePrice() throws Exception {
        String requestJson =
                "{\"name\":\"Laptop\",\"price\":0}";

        mockMvc.perform(post("/api/validated-products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(requestJson))
                .andExpect(status().isBadRequest());
    }
}

These tests verify that validation failures are rejected. A production API should also return a predictable error response containing useful validation details without exposing internal implementation information.

15. Testing Error Handling

Applications commonly use @RestControllerAdvice and @ExceptionHandler to convert exceptions into consistent HTTP responses. Integration tests should verify the externally visible error contract.

import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.http.ResponseEntity;

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, String>> handleBadRequest(
            IllegalArgumentException exception) {

        Map<String, String> body = Map.of(
                "error", "BAD_REQUEST",
                "message", exception.getMessage());

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(body);
    }
}

This is an illustrative handler. In a real application, avoid exposing sensitive exception details. Prefer stable error codes and safe messages that clients can process consistently.

Example Error Response

{
  "error": "BAD_REQUEST",
  "message": "The supplied product details are invalid"
}

A test should verify the expected status and response fields when the relevant endpoint triggers the exception.

16. Testing REST APIs with a Real HTTP Server

MockMvc tests exercise Spring MVC without starting a real HTTP server. Sometimes you need to verify the application through an actual HTTP connection. In that case, use @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT).

import org.junit.jupiter.api.Test;
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.test.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
class ProductHttpIntegrationTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldReturnNotFoundForUnknownProduct() {
        String url = "http://localhost:" + port
                + "/api/products/999";

        ResponseEntity<String> response =
                restTemplate.getForEntity(url, String.class);

        assertEquals(404, response.getStatusCode().value());
    }
}

The embedded server starts on a randomly assigned available port. The test sends an actual HTTP request to that server and checks the response.

When to Use Each Approach

  • Use MockMvc when you need fast tests of Spring MVC routing, request handling, serialization, and configured web filters without a real server.
  • Use a random-port server test when actual HTTP communication or embedded-server behavior is important.
  • Use a real test database when persistence behavior, database queries, and transaction configuration must be verified.

17. Testing Database Integration

For APIs backed by JPA or JDBC, integration tests should verify that the application can save, retrieve, update, and delete data correctly. A dedicated test database helps isolate tests from production data.

17.1 Example Test Database Configuration

# src/test/resources/application-test.properties
spring.datasource.url=jdbc:h2:mem:integrationdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop

This is an example configuration for a test environment. Confirm that the H2 dependency and appropriate JPA dependencies are present. For systems that depend on database-specific SQL, consider running integration tests against the same database engine used in production.

17.2 Activate the Test Profile

import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class ProductDatabaseIntegrationTest {
    // Inject MockMvc and test repositories or API endpoints.
}

Place test-specific configuration in the test resources directory. Avoid copying production secrets or pointing tests at production databases.

17.3 Database Testing Best Practices

  • Use isolated test data and avoid dependencies on existing records.
  • Clean up inserted records or use a controlled transaction strategy.
  • Verify persistence by reading data back through the intended application path.
  • Test constraints, duplicate values, and missing records.
  • Consider Testcontainers when the production database engine must be reproduced in tests.
  • Do not assume that transaction rollback in a test will undo work performed in a separate thread or an external system.

18. Testing Security on REST Endpoints

If Spring Security protects an API, integration tests should verify which requests are allowed and which require authentication or specific permissions.

For a secured endpoint, common scenarios include:

  • A request without authentication is rejected when authentication is required.
  • An authenticated user with insufficient permissions receives a forbidden response.
  • A user with the required role can access the endpoint.
  • Invalid or expired credentials are handled correctly.
  • CSRF requirements are tested appropriately for the application's authentication and request configuration.

Use Spring Security's test support where appropriate. Do not disable all security configuration simply to make integration tests pass, because that may hide defects in the real request-processing pipeline.

19. Mocking External Services During Integration Tests

Not every dependency should be real in an integration test. For example, an API may call an external payment gateway, email provider, or third-party shipping service. Tests can keep the application's own components real while replacing external systems with controlled test doubles or mock HTTP servers.

Choose the boundary deliberately:

  • Keep controller, service, repository, and test database behavior real when these are the components being tested.
  • Mock external dependencies when the test should not make real network calls or trigger real charges and notifications.
  • Use a mock HTTP server when you need to verify the format and handling of outgoing HTTP requests.
  • Include separate contract or end-to-end tests when compatibility with an external service must be verified.

Integration testing does not mean that every external system must be contacted. The goal is to verify the intended interactions across a clearly defined application boundary.

20. Organizing Integration Test Data

Reliable tests should control the data they use. A test should not fail simply because another test ran first or left behind unexpected records.

Common Strategies

  • Arrange data in each test: Create the minimum records needed before sending requests.
  • Use unique identifiers: Avoid collisions when tests share a database.
  • Clean up after testing: Delete records or reset the test environment when appropriate.
  • Use test fixtures: Centralize reusable setup data without hiding important test assumptions.
  • Keep tests independent: Each test should establish its own preconditions.
  • Control external effects: Prevent test runs from sending real customer emails or processing real payments.

For tests using an in-memory service or database, remember that application-context caching and shared state can affect multiple test methods. Design cleanup and test isolation deliberately.

21. Running Integration Tests with Maven and Gradle

21.1 Maven

mvn test

This runs the tests configured in the Maven project, subject to its test plugins and naming conventions.

21.2 Gradle

./gradlew test

On Windows, the command is typically:

gradlew.bat test

Continuous Integration Recommendations

  • Run unit tests on every relevant code change.
  • Run integration tests in the CI pipeline with isolated configuration.
  • Store test reports and investigate failures rather than ignoring them.
  • Use a predictable test database and controlled external-service substitutes.
  • Separate long-running end-to-end tests when they require special infrastructure.

22. Common Problems and Troubleshooting

Problem Possible Cause Solution
Application context fails to start Missing bean, invalid configuration, or unavailable dependency. Inspect the root cause in the test logs and verify component scanning and test configuration.
Endpoint returns 404 unexpectedly Incorrect route, context path, HTTP method, or controller registration. Verify the request URL and controller mappings.
JSON fields are missing Serialization configuration or incorrect response model. Inspect the actual response body and Jackson configuration.
Expected 400 but receives 200 Validation annotations are missing, validation is not activated, or the test uses the wrong request body. Check DTO constraints, validation dependencies, and controller parameter annotations.
Test data differs between runs Tests share state or depend on execution order. Arrange and clean up test data for each test.
Database tests fail on startup Incorrect JDBC configuration or missing database dependency. Check the test profile, database URL, driver, and JPA settings.
Security tests receive 401 or 403 Authentication, roles, or CSRF requirements are not satisfied. Use the intended security test setup and provide the correct credentials or permissions.
Test is slow Repeated context startup, unnecessary external calls, or large datasets. Choose the smallest suitable test scope and isolate external dependencies.
Port conflict occurs Multiple tests use a fixed port. Prefer a random server port for real HTTP integration tests.
Test passes alone but fails in a suite Shared state, order dependence, or asynchronous work remains active. Remove test dependencies, isolate data, and synchronize background operations properly.

23. Best Practices for REST API Integration Testing

  • Test APIs through HTTP requests or the appropriate Spring web testing interface instead of calling controller methods directly.
  • Use MockMvc for Spring MVC tests that do not require a real server.
  • Use random-port tests for scenarios that require an actual HTTP connection.
  • Assert status codes, response content, and important headers.
  • Cover both successful requests and negative scenarios.
  • Use test databases or isolated containers for persistence testing.
  • Keep test data deterministic and independent.
  • Mock external services where real calls would be unsafe, costly, or unreliable.
  • Test authentication, authorization, validation, and exception handling.
  • Avoid exposing production credentials or personal data in tests and logs.
  • Keep unit tests and integration tests distinct so failures are easier to diagnose.
  • Run the suite in continuous integration and review test reports regularly.

24. Practical Project: Product Management REST API

Project Objective

Build an integration test suite for a product management API using Spring Boot, MockMvc, JUnit 5, and a test database.

Functional Requirements

  1. Create a product with a name and price.
  2. Retrieve a product by its generated ID.
  3. Return 404 when the requested product does not exist.
  4. Reject invalid product names and non-positive prices.
  5. Update an existing product.
  6. Delete a product and verify that it is no longer available.
  7. Persist product records through a repository.
  8. Return consistent error responses for invalid requests.
  9. Verify security rules if the endpoints are protected.
  10. Run all tests through Maven or Gradle.

Suggested Package Structure

com.example.productapi
├── ProductApiApplication.java
├── controller
│   └── ProductController.java
├── service
│   └── ProductService.java
├── repository
│   └── ProductRepository.java
├── model
│   └── Product.java
├── dto
│   └── CreateProductRequest.java
├── exception
│   └── ApiExceptionHandler.java
└── test
    └── ProductApiIntegrationTest.java

Expected Learning Outcomes

  • Understand how requests travel through a Spring Boot REST application.
  • Validate HTTP behavior using MockMvc.
  • Test JSON serialization, request validation, and error handling.
  • Verify integration between services, repositories, and a test database.
  • Build repeatable tests suitable for a continuous integration pipeline.

25. Interview Questions and Answers

Q1. What is integration testing?

Answer: Integration testing verifies that multiple components work together correctly, such as a controller, service, repository, and database.

Q2. What is the difference between unit testing and integration testing?

Answer: Unit tests focus on isolated logic and often mock dependencies. Integration tests exercise interactions between real components and may use real infrastructure in a controlled test environment.

Q3. What is @SpringBootTest?

Answer: It loads a Spring Boot application context for testing and can be configured with different web environments.

Q4. What is MockMvc?

Answer: MockMvc tests Spring MVC request processing without starting a real HTTP server.

Q5. Does MockMvc test a real HTTP connection?

Answer: No. It exercises the Spring MVC infrastructure in a simulated web environment. Use a random-port server test for actual HTTP communication.

Q6. What is the purpose of @AutoConfigureMockMvc?

Answer: It configures MockMvc for a Spring Boot test context so tests can send simulated requests to MVC endpoints.

Q7. What is the difference between @WebMvcTest and @SpringBootTest?

Answer: @WebMvcTest focuses on the web layer and typically limits the loaded components. @SpringBootTest loads a broader application context.

Q8. How can you test a POST endpoint?

Answer: Use MockMvc's post() request builder, provide the appropriate content type and JSON body, then assert the status and response content.

Q9. How do you test validation failures?

Answer: Submit invalid input and verify that the endpoint returns the expected client-error status and structured error response.

Q10. Why use a test database?

Answer: It allows persistence behavior to be tested without relying on production data and helps make the test environment repeatable.

Q11. When should a real server be started in an integration test?

Answer: Start a server when the test needs to verify actual HTTP communication, embedded-server behavior, or other concerns that MockMvc does not exercise.

Q12. Should every external API be called during integration tests?

Answer: No. External dependencies can be replaced with controlled test doubles or mock HTTP servers. Separate contract or end-to-end tests can verify external compatibility when required.

Q13. How do you avoid test data conflicts?

Answer: Use isolated test data, unique identifiers, cleanup strategies, and independent test setup.

Q14. What should an API integration test assert?

Answer: It should verify relevant status codes, response bodies, headers, validation behavior, persistence effects, and important security rules.

Q15. Why are integration tests important in CI/CD?

Answer: They detect failures in component interactions before deployment and provide repeatable evidence that important API workflows continue to work.

26. Practice Exercises

  1. Write a GET integration test that retrieves an existing product.
  2. Write a test that verifies HTTP 404 for a missing product.
  3. Create a POST test that checks the 201 Created status.
  4. Verify response JSON fields using jsonPath().
  5. Test a request with a missing required field.
  6. Test a request containing an invalid price.
  7. Configure a test database and verify that a created product can be retrieved.
  8. Write a random-port test that sends an actual HTTP request.
  9. Test a secured endpoint with and without valid authorization.
  10. Run the test suite in a CI pipeline and review the generated report.

27. Quick Revision Checklist

  • Integration tests verify interactions between application components.
  • @SpringBootTest loads a Spring Boot application context.
  • @AutoConfigureMockMvc configures MockMvc for Spring Boot tests.
  • MockMvc tests Spring MVC without a real HTTP server.
  • RANDOM_PORT starts an embedded server on an available port.
  • HTTP status codes and JSON response fields should be asserted.
  • Request validation and exception handling need negative tests.
  • A test database helps verify repository and persistence behavior.
  • External systems can be mocked when direct calls are unnecessary or unsafe.
  • Tests should use controlled data and avoid dependencies on execution order.
  • Security behavior should be tested rather than disabled indiscriminately.
  • Run integration tests regularly through Maven or Gradle and CI/CD.

28. Conclusion

Integration testing is essential for confirming that REST APIs behave correctly when their components work together. Spring Boot provides flexible approaches for testing API endpoints, including MockMvc for simulated MVC requests and random-port tests for real HTTP communication.

Effective integration tests cover the complete request and response flow, validation, error handling, security, persistence, and interactions with external systems. By keeping test data isolated and selecting the right test scope, developers can build reliable APIs and catch integration defects before deployment.

29. Additional Learning Resources

Previous Next
whatsapp