Testing is a crucial part of any application development process, especially when building RESTful APIs with Spring Boot. In this blog post, we’ll walk you through a practical example of testing CRUD (Create, Read, Update, Delete) REST APIs for a Person entity using Spring Data JPA with H2 as the database. We’ll create a Maven project from scratch using Spring Initializr, write JUnit 5 tests, and demonstrate how to test the PersonController using MockMvc. Let’s get started!
- Prerequisites
- Creating a Maven Project
- Creating the Entity:
Person
- Creating the Repository:
PersonRepository
- Implementing the CRUD REST API
- Run the Application
- Access the Application from Terminal/CLI
- Writing JUnit Tests with MockMvc
- Conclusion
Prerequisites
If you don’t already have Maven installed, you can download it from the official Maven website https://maven.apache.org/download.cgi or through SDKMAN https://sdkman.io/sdks#maven
You can clone the https://github.com/dmakariev/examples
repository.
git clone https://github.com/dmakariev/examples.git
cd examples/spring-boot/crud-mockmvc
Creating a Maven Project
First, let’s create a new Spring Boot project with Spring Initializr using the terminal.
- Open your terminal and navigate to the directory where you want to create your project.
- Run the following command to generate a new Maven project:
curl https://start.spring.io/starter.tgz -d packaging=jar \ -d dependencies=data-jpa,web,h2,lombok \ -d baseDir=crud-mockmvc -d artifactId=crud-mockmvc \ -d name=crud-mockmvc -d type=maven-project \ -d groupId=com.makariev.examples.spring | tar -xzvf -
- This command will download the Spring Initializr project and extract it into a directory named crud-mockmvc.
- Next, open the project in your favorite IDE (such as NetBeans, IntelliJ IDEA, VSCode or Eclipse) to proceed.
Creating the Entity: Person
We’ll create a simple Person
entity with firstName
, lastName
, and birthYear
fields.
package com.makariev.examples.spring.crudmockmvc.person;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data // generates getters, setters, equals and hashCode and constructor
@NoArgsConstructor //generates default constructor
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private int birthYear;
public Person(String firstName, String lastName, int birthYear) {
this.firstName = firstName;
this.lastName = lastName;
this.birthYear = birthYear;
}
}
Creating the Repository: PersonRepository
package com.makariev.examples.spring.crudmockmvc.person;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PersonRepository extends JpaRepository<Person, Long> {
}
Implementing the CRUD REST API
Now, let’s create a RESTful CRUD API to manage Person
entities using the PersonController
. The controller will handle HTTP requests to perform operations like creating, retrieving, updating, and deleting Person
objects.
package com.makariev.examples.spring.crudmockmvc.person;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor // generates constructor for all 'final' fields
@RestController
@RequestMapping("/api/persons")
public class PersonController {
private final PersonRepository personRepository;
@GetMapping
public Page<Person> findAll(Pageable pageable) {
return personRepository.findAll(pageable);
}
@GetMapping("{id}")
public Optional<Person> findById(@PathVariable("id") Long id) {
return personRepository.findById(id);
}
@PostMapping
@ResponseStatus( HttpStatus.CREATED )
public Person create(@RequestBody Person person) {
return personRepository.save(person);
}
@PutMapping("{id}")
public Person updateById(@PathVariable("id") Long id, @RequestBody Person person) {
var loaded = personRepository.findById(id).orElseThrow();
loaded.setFirstName(person.getFirstName());
loaded.setLastName(person.getLastName());
loaded.setBirthYear(person.getBirthYear());
return personRepository.save(loaded);
}
@DeleteMapping("/{id}")
@ResponseStatus( HttpStatus.NO_CONTENT )
public void deleteById(@PathVariable("id") Long id) {
personRepository.deleteById(id);
}
}
Run the Application
Return to your terminal. Navigate to the directory containing your project. Execute
./mvnw spring-boot:run
Access the Application from Terminal/CLI
Open your web browser and navigate to http://localhost:8080/hi
. You should see the “Hello, World!” message displayed in your browser.
Or if you prefer more personalized message, then navigate to http://localhost:8080/hi?name=Joe
. You should see the “Hello, Joe!” message displayed in your browser.
Using the Rest Api with Curl
To create a new person, use the POST method with the person data as a JSON body:
curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"Katherine", "lastName":"Johnson", "birthYear":1919}' \
http://localhost:8080/api/persons
To get a list of persons, use the GET method:
curl -X GET http://localhost:8080/api/persons
To get a list of persons, from page 1, when the page size is 1
curl -X GET http://localhost:8080/api/persons -G -d page=0 -d size=1
To get a list of persons, sorted by firstName
:
curl -X GET http://localhost:8080/api/persons -G -d sort=firstName,asc
curl -X GET http://localhost:8080/api/persons -G -d sort=firstName,desc
curl -X GET http://localhost:8080/api/persons -G -d sort=firstName
To get a list of persons, from page 3, when the page size is 1, sorted by firstName
curl -X GET http://localhost:8080/api/persons -G -d page=2 -d size=1 -d sort=firstName,asc
To get a list of persons, sorted by firstName
and lastName
:
curl -X GET http://localhost:8080/api/persons -G -d sort=firstName,asc -d sort=lastName,desc
curl -X GET http://localhost:8080/api/persons -G -d sort=firstName,desc -d sort=lastName,asc
curl -X GET http://localhost:8080/api/persons -G -d sort=firstName -d sort=lastName
To get a specific person by id, use the GET method with the id as a path variable:
curl -X GET http://localhost:8080/api/persons/1
To update an existing person by id, use the PUT method with the person data as a JSON body:
curl -X PUT -H "Content-Type: application/json" \
-d '{"firstName":"Katherine", "lastName":"Johnson", "birthYear":1918}' \
http://localhost:8080/api/persons/1
Using the Rest Api with HTTPIE
you could download alternative Terminal/CLI client from here https://httpie.io/cli
To create a new person, use the POST method with the person data as a JSON body:
http POST http://localhost:8080/api/persons firstName=Alice lastName=Smith birthYear=1996
To get a list of persons, use the GET method:
http GET http://localhost:8080/api/persons
To get a list of persons, from page 1, when the page size is 1
http GET http://localhost:8080/api/persons page==0 size==1
To get a list of persons, sorted by firstName
:
http GET http://localhost:8080/api/persons sort==firstName,asc
http GET http://localhost:8080/api/persons sort==firstName,desc
http GET http://localhost:8080/api/persons sort==firstName
To get a list of persons, from page 3, when the page size is 1, sorted by firstName
http GET http://localhost:8080/api/persons page==2 size==1 sort==firstName,asc
To get a list of persons, sorted by firstName
and lastName
:
http GET http://localhost:8080/api/persons sort==firstName,asc sort==lastName,desc
http GET http://localhost:8080/api/persons sort==firstName,desc sort==lastName,asc
http GET http://localhost:8080/api/persons sort==firstName sort==lastName
To get a specific person by id, use the GET method with the id as a path variable:
http GET http://localhost:8080/api/persons/1
To update an existing person by id, use the PUT method with the person data as a JSON body:
http PUT http://localhost:8080/api/persons/1 firstName=Bob lastName=Jones birthYear=1990
To delete an existing person by id, use the DELETE method with the id as a path variable:
http DELETE http://localhost:8080/api/persons/1
Writing JUnit Tests with MockMvc
We’ll create JUnit 5 tests for the PersonController
using MockMvc
, which allows us to simulate HTTP requests and validate the responses.
Let’s create a test class PersonControllerTest
in the src/test/java/com/makariev/examples/spring/crudmockmvc/person
directory:
package com.makariev.examples.spring.crudmockmvc.person;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class PersonControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private PersonRepository personRepository;
// Test method for creating a new Person
@Test
void shouldCreateNewPerson() throws Exception {
// Create a new Person instance
final Person person = new Person();
person.setFirstName("John");
person.setLastName("Doe");
person.setBirthYear(1980);
// Perform a POST request to create the Person
mockMvc.perform(post("/api/persons")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(person)))
.andDo(print())
.andExpect(status().isCreated());
// Verify that the Person was created in the database
assertThat(personRepository.count()).isEqualTo(1);
//clean the database
personRepository.deleteAll();
}
// Test method for retrieving a Person by ID
@Test
void shouldRetrievePersonById() throws Exception {
// Create a new Person and save it in the database
final Person savedPerson = personRepository.save(new Person("Alice", "Smith", 1990));
// Perform a GET request to retrieve the Person by ID
mockMvc.perform(get("/api/persons/{id}", savedPerson.getId()))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.firstName", is(savedPerson.getFirstName())))
.andExpect(jsonPath("$.lastName", is(savedPerson.getLastName())))
.andExpect(jsonPath("$.birthYear", is(savedPerson.getBirthYear())));
//clean the database
personRepository.delete(savedPerson);
}
// Test method for updating a Person
@Test
void shouldUpdatePerson() throws Exception {
// Create a new Person and save it in the database
final Person savedPerson = personRepository.save(new Person("Bob", "Johnson", 1985));
// Update the Person's information
savedPerson.setFirstName("UpdatedFirstName");
savedPerson.setLastName("UpdatedLastName");
// Perform a PUT request to update the Person
mockMvc.perform(put("/api/persons/{id}", savedPerson.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(savedPerson)))
.andDo(print())
.andExpect(status().isOk());
// Verify that the Person's information was updated in the database
final Person updatedPerson = personRepository.findById(savedPerson.getId()).orElse(null);
assertThat(updatedPerson).isNotNull();
assertThat(updatedPerson.getFirstName()).isEqualTo("UpdatedFirstName");
assertThat(updatedPerson.getLastName()).isEqualTo("UpdatedLastName");
//clean the database
personRepository.delete(savedPerson);
}
// Test method for deleting a Person
@Test
void shouldDeletePerson() throws Exception {
// Create a new Person and save it in the database
final Person savedPerson = personRepository.save(new Person("Eve", "Williams", 2000));
// Perform a DELETE request to delete the Person by ID
mockMvc.perform(delete("/api/persons/{id}", savedPerson.getId()))
.andDo(print())
.andExpect(status().isNoContent());
// Verify that the Person was deleted from the database
assertThat(personRepository.existsById(savedPerson.getId())).isFalse();
}
// Test method for retrieving a list of persons
@Test
void shouldRetrievePersonList() throws Exception {
// Create a new persons and save them in the database
List.of(
new Person("Alice", "Smith", 1990),
new Person("Ada", "Lovelace", 1815),
new Person("Niklaus", "Wirth", 1934),
new Person("Donald", "Knuth", 1938),
new Person("Edsger", "Dijkstra", 1930),
new Person("Grace", "Hopper", 1906),
new Person("John", "Backus", 1924)
).forEach(personRepository::save);
// Perform a GET request to retrieve list of persons
mockMvc.perform(
get("/api/persons")
.param("page", "0")
.param("size", "1")
).andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.totalElements", is(7)))
.andExpect(jsonPath("$.numberOfElements", is(1)));
// Perform a GET request to retrieve list of persons,
// from page 3, when the page size is 1, sorted by `firstName`
mockMvc.perform(
get("/api/persons")
.param("page", "2")
.param("size", "1")
.param("sort", "firstName,asc")
).andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.totalElements", is(7)))
.andExpect(jsonPath("$.numberOfElements", is(1)))
.andExpect(jsonPath("$.content[0].firstName", is("Donald")));
//clean the database
personRepository.deleteAll();
}
}
Running the Test
To run the test, execute the following command in the project’s root directory:
./mvnw test
JUnit 5 and AssertJ will execute the test, and you should see output indicating whether the test passed or failed.
Conclusion
In this blog post, we’ve demonstrated how to create a Spring Boot project for testing CRUD REST APIs using MockMvc. We’ve set up a Person
entity, implemented the CRUD operations in the PersonController
, and written JUnit tests in PersonControllerTest.java
to validate the API endpoints. Proper testing ensures the reliability and correctness of your RESTful services.
Happy coding!