Unit Testing Rest Services with Spring Boot and JUnit

blog details
author Ranga Karanam September 06, 2025 9 minutes

This article will guide you through writing effective unit tests for your Spring Boot REST services.

To keep things simple and practical, we’ll start by creating a few basic REST endpoints using a sample Spring Boot project. Once the services are ready, we’ll explore how to write clean, reliable unit tests for them.

Image

You will learn

  • What Unit Testing is and why it matters
  • How to create a GET REST Service to retrieve the courses registered by a student
  • How to write a Unit Test for the GET REST Service
  • How to create a POST REST Service to register a course for a student
  • How to write a Unit Test for the POST REST Service

Tools you will need

  • Maven 3.0+ – Build and dependency management tool
  • An IDE of your choice – Eclipse, IntelliJ IDEA, or any other Java IDE
  • JDK 17+ – Java Development Kit to compile and run the application

Complete Maven Project With Code Examples

You can find all the code examples in our GitHub repository:
https://github.com/in28minutes/in28minutes.github.io/tree/master/code-zip-files

  • REST Services with Unit and Integration Tests
    👉 Download: Website-springbootrestservices-simplerestserviceswithunitandintegrationtests.zip

Unit Testing

The following screenshot shows the Eclipse project structure with all the files we will create as part of this tutorial.

We will cover:

  • Writing a simple GET REST Service and its unit test
  • Writing a POST REST Service and its unit test

Image

Writing Unit Tests for StudentController

The StudentController exposes two service methods — GET and POST.
We will create unit tests for both of these methods.

In the unit tests:

  • The StudentService dependency will be mocked using Mockito.
  • We will use the MockMvc framework to launch only the StudentController.

⚡ Key Principle:
A critical aspect of unit testing is limiting the scope.
Here, we only want to test the logic inside StudentController, without involving the actual StudentService implementation or other layers.

Overview

In this guide, we will walk through the process of building and testing a simple Spring Boot REST API.
The steps we will follow are:

  1. Bootstrap the Project
    Use Spring Initializr to quickly set up the base project.

  2. Implement the Business Service
    Create the StudentService class to provide business logic for our API.

  3. Build the REST API
    Develop the StudentController:
    • First, implement the GET endpoints.
    • Then, implement the POST endpoint.
  4. Write Unit Tests
    Use Mockito and MockMvc to unit test the StudentController.

Bootstrap REST Services Application with Spring Initializr

Spring Initializr is an excellent tool for bootstrapping Spring Boot projects with just a few clicks.

With Spring Initializr, you can quickly generate a project structure by selecting:

  • Group: com.in28minutes.springboot
  • Artifact: student-services
  • Dependencies:
    • Spring Web
    • Spring Boot Actuator
    • Spring Boot DevTools

Once generated, download the project, unzip it, and import it into your favorite IDE (Eclipse, IntelliJ, or VS Code).

Image

As shown in the image above, follow these steps to create your project:

  • Launch Spring Initializr and choose the following:
    • Group: com.in28minutes.springboot
    • Artifact: student-services
    • Dependencies:
      • Spring Web
      • Spring Boot Actuator
      • Spring Boot DevTools
  • Click Generate Project to download the starter project.
  • Import the project into Eclipse (File → Import → Existing Maven Project).
  • To explore and understand all the files generated by Spring Initializr, you can refer here.

Adding Business Services to Your Application

Every application needs data. In this example, instead of connecting to a real database, we’ll use an ArrayList as an in-memory data store.

  • A student can enroll in multiple courses.
  • A course has an id, name, description, and a list of steps to complete the course.
  • A student has an id, name, description, and a list of registered courses.

We’ll implement a StudentService that provides the following methods:

  • public List<Student> retrieveAllStudents() – Retrieve details for all students
  • public Student retrieveStudent(String studentId) – Retrieve details of a specific student
  • public List<Course> retrieveCourses(String studentId) – Retrieve all courses a student is registered for
  • public Course retrieveCourse(String studentId, String courseId) – Retrieve a specific course for a student
  • public Course addCourse(String studentId, Course course) – Add a new course for an existing student

You can find the actual implementation of the service and models in these files:

  • src/main/java/com/in28minutes/springboot/model/Course.java
  • src/main/java/com/in28minutes/springboot/model/Student.java
  • src/main/java/com/in28minutes/springboot/service/StudentService.java

Adding a Couple of GET Operations

The StudentController exposes two GET REST endpoints:

private final StudentService studentService;

public StudentController(StudentService studentService) {
    this.studentService = studentService;
}
  • Uses Spring Dependency Injection to wire the StudentService into the controller.
  • @GetMapping("/students/{studentId}/courses") – Retrieves all courses for a given student (studentId is passed as a path variable).
  • @GetMapping("/students/{studentId}/courses/{courseId}") – Retrieves details of a specific course (courseId) for a student.
  • @PathVariable String studentId – Maps the value of studentId from the URI to this parameter.
package com.in28minutes.springboot.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.in28minutes.springboot.model.Course;
import com.in28minutes.springboot.service.StudentService;

@RestController
public class StudentController {

    private final StudentService studentService;

    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }

    @GetMapping("/students/{studentId}/courses")
    public List<Course> retrieveCoursesForStudent(@PathVariable String studentId) {
        return studentService.retrieveCourses(studentId);
    }
    
    @GetMapping("/students/{studentId}/courses/{courseId}")
    public Course retrieveDetailsForCourse(@PathVariable String studentId,
            @PathVariable String courseId) {
        return studentService.retrieveCourse(studentId, courseId);
    }

}

Executing the Http Get Operation Using Postman

We will fire a request to http://localhost:8080/students/Student1/courses/Course1 to test the service. Response is as shown below.

{
  "id": "Course1",
  "name": "Spring",
  "description": "10Steps",
  "steps": [
    "Learn Maven",
    "Import Project",
    "First Example",
    "Second Example"
  ]
}

Below screenshot demonstrates how to execute this GET operation using Postman — a popular tool for testing RESTful services.

Image

Add spring-security-test for disabling security in unit tests

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

Unit Testing Http Get Operation

Unit Testing a Spring MVC Controller

When unit testing a REST service, we want to launch only the relevant controller and the associated MVC components.
The annotation @WebMvcTest is used for this purpose. It focuses the test exclusively on Spring MVC components. Using this annotation disables full auto-configuration and loads only the configuration relevant for MVC tests.

Key components in our test setup:

  • @ExtendWith(SpringExtension.class)
    • Registers Spring extensions with JUnit 5.
    • Integrates the Spring TestContext Framework into JUnit Jupiter.
    • Supports annotated arguments in constructors, test methods, and lifecycle methods (@BeforeAll, @BeforeEach, @AfterAll, @AfterEach).
  • @WebMvcTest(value = StudentController.class)
    • Used to unit test Spring MVC components.
    • Launches only StudentController for testing.
    • No other controllers or mappings are started.
  • @Autowired private MockMvc mockMvc
    • Entry point for server-side Spring MVC test support.
    • Allows executing HTTP requests against the test context.
  • @MockBean private StudentService studentService
    • Mocks the StudentService and injects it into StudentController.
    • Ensures the unit test only focuses on controller behavior.
  • Mockito.when(studentService.retrieveCourse(Mockito.anyString(), Mockito.anyString())).thenReturn(mockCourse)
    • Mocks the behavior of retrieveCourse() to return a predefined mockCourse.
  • MockMvcRequestBuilders.get("/students/Student1/courses/Course1").accept(MediaType.APPLICATION_JSON)
    • Creates a GET request to the specified URI with an Accept header of application/json.
  • mockMvc.perform(requestBuilder).andReturn()
    • Executes the request using MockMvc and returns the response.
  • JSONAssert.assertEquals(expected, result.getResponse().getContentAsString(), false)
    • Uses org.skyscreamer.jsonassert.JSONAssert to assert JSON responses.
    • Passing strict=false allows partial checks without requiring all fields to match.
package com.in28minutes.springboot.controller;

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

import java.util.Arrays;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import com.in28minutes.springboot.model.Course;
import com.in28minutes.springboot.service.StudentService;

@ExtendWith(SpringExtension.class)
@WebMvcTest(value = StudentController.class)
@WithMockUser
public class StudentControllerTest {

	@Autowired
	private MockMvc mockMvc;

	@MockBean
	private StudentService studentService;

	Course mockCourse = new Course("Course1", "Spring", "10Steps",
			Arrays.asList("Learn Maven", "Import Project", "First Example", "Second Example"));

	String exampleCourseJson = "{\"name\":\"Spring\",\"description\":\"10Steps\",\"steps\":[\"Learn Maven\",\"Import Project\",\"First Example\",\"Second Example\"]}";

	@Test
	public void retrieveDetailsForCourse() throws Exception {

		Mockito.when(studentService.retrieveCourse(Mockito.anyString(),
						Mockito.anyString())).thenReturn(mockCourse);

		RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
				"/students/Student1/courses/Course1").accept(
				MediaType.APPLICATION_JSON);

		MvcResult result = mockMvc.perform(requestBuilder).andReturn();

		System.out.println(result.getResponse());
		String expected = "{\"id\":\"Course1\",\"name\":\"Spring\",\"description\":\"10 Steps\"}";


		// {"id":"Course1","name":"Spring","description":"10 Steps, 25 Examples and 10K Students","steps":["Learn Maven","Import Project","First Example","Second Example"]}

		JSONAssert.assertEquals(expected, result.getResponse()
				.getContentAsString(), false);
	}

}

Adding Http POST Operation

Implementing a HTTP POST Operation

An HTTP POST operation should return a 201 Created status when the resource is successfully created.

Key components of the POST implementation:

  • @PostMapping("/students/{studentId}/courses")
    Maps the URL to handle POST requests for adding a course to a student.

  • @RequestBody Course newCourse
    Binds the JSON body of the request to a Course object.

  • ResponseEntity.created(location).build()
    Returns a 201 Created status and sets the Location header pointing to the URI of the newly created resource.

Example:
When adding a course to Student1, the POST request might look like:

@PostMapping("/students/{studentId}/courses")
public ResponseEntity<Void> registerStudentForCourse(
        @PathVariable String studentId, @RequestBody Course newCourse) {

    Course course = studentService.addCourse(studentId, newCourse);

    if (course == null)
        return ResponseEntity.noContent().build();

    URI location = ServletUriComponentsBuilder.fromCurrentRequest().path(
            "/{id}").buildAndExpand(course.getId()).toUri();

    return ResponseEntity.created(location).build();
}

Executing the Http POST Operation

An example request is shown below. It provides all of the information needed to enrol a student for a course.

{
  "name": "Microservices",
  "description": "10Steps",
  "steps": [
    "Learn How to Break Things Up",
    "Automate the hell out of everything",
    "Have fun"
  ]
}

Executing the HTTP POST Operation Using Postman

The image below demonstrates how to perform this POST operation using Postman — my preferred tool for testing REST services.

Steps to follow:

  1. Go to the Body tab in Postman.
  2. Select raw.
  3. From the dropdown menu, choose JSON.
  4. Copy and paste the JSON request (shown above) into the body.
  5. Click Send to execute the POST request.

If successful, the server will return a 201 Created status along with the location of the newly created resource in the response headers.

The URL we use is http://localhost:8080/students/Student1/courses.

Image

Writing Unit Test for the Http POST Operation

Unit Testing the HTTP POST Operation

In the unit test, we want to send a POST request to /students/Student1/courses and validate that:

  1. The HTTP status returned is 201 Created.
  2. The Location header contains the URI of the newly created resource.

Key components in the test:

  • MockMvcRequestBuilders.post("/students/Student1/courses").accept(MediaType.APPLICATION_JSON)
    Creates a POST request with an Accept header set to application/json.

  • content(exampleCourseJson).contentType(MediaType.APPLICATION_JSON)
    Sets the request body to exampleCourseJson and specifies the content type as JSON.

  • assertEquals(HttpStatus.CREATED.value(), response.getStatus())
    Verifies that the response status is 201 Created.

  • response.getHeader(HttpHeaders.LOCATION)
    Retrieves the Location header from the response. You can then assert that it contains the URI of the newly created course.

	@Test
	public void createStudentCourse() throws Exception {
		var mockCourse = new Course("1", "Smallest Number", "1", Arrays.asList("1", "2", "3", "4"));

		// studentService.addCourse to respond back with mockCourse
		Mockito.when(studentService.addCourse(Mockito.anyString(),
						Mockito.any(Course.class))).thenReturn(mockCourse);

		// Send course as body to /students/Student1/courses
		RequestBuilder requestBuilder = MockMvcRequestBuilders
				.post("/students/Student1/courses")
				.accept(MediaType.APPLICATION_JSON).content(exampleCourseJson)
				.contentType(MediaType.APPLICATION_JSON);

		MvcResult result = mockMvc.perform(requestBuilder).andReturn();

		MockHttpServletResponse response = result.getResponse();

		assertEquals(HttpStatus.CREATED.value(), response.getStatus());

		assertEquals("http://localhost/students/Student1/courses/1",
				response.getHeader(HttpHeaders.LOCATION));

	}

Just Released