Spring Boot - Testing a MongoDB application with @DataMongoTest - Example
In this section, we will learn how to test Repository layer components with @DataMongoTest in Spring Boot application.
1. @DataMongoTest
Instead of bootstrapping the entire application context for every test, @DataMongoTest allows us to initialize the Spring application context with only those beans needed to test Data MongoDB-based components.
By default, it scans for @Document classes and configures Spring Data MongoDB repositories. It will auto-configure MongoTemplate. If an embedded database is available on the classpath, @DataMongoTest will autoconfigure one for testing purposes.
Regular @Component, @Service or @Controller beans are not scanned when using this annotation.
This approach not only speeds up the testing process but also ensures a focused and efficient testing environment.
This approach is also known as "slicing" the application context.
Find the sample code snippet to use @DataMongoTest annotation in unit test class.
@DataMongoTest
public class StudentRepositoryTest {
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private StudentRepository studentRepository;
@Test
void findByName_ReturnsTheStudent() {
//todo...
}
}
By using an embedded MongoDB instance, developers can run tests in isolation, it is fast, and does not require extra installation for setting up test environment.
2. Test Against a Real MongoDB
If you want run the tests against an application configured real database, you should exclude the embedded MongoDB auto-configuration.
@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
public class StudentRepositoryTest {
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private StudentRepository studentRepository;
@Test
void findByName_ReturnsTheStudent() {
//todo...
}
}
Complete example
Next we will create a spring Data MongoDB application, create Repository layer which contains three methods and finally we will write test against embedded MongoDB instance.
3. Creating spring boot application
First, open the Spring initializr https://start.spring.io
Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact datamongotest-example. Here I selected the Maven project - language Java 17 - Spring Boot 3.1.5 , and Spring Data MongoDB.
Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(datamongotest-example) file and downloads the project. Then, Extract the Zip file.
Then, import the project on your favourite IDE.
Final Project directory:
For Spring Boot 3, you can use the embedded MongoDB Spring 3.x integration from the third party.
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.6.2</version>
<scope>test</scope>
</dependency>
Complete pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>datamongotest-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>datamongotest-example</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
</image>
</configuration>
</plugin>
</plugins>
</build>
</project>
spring-boot-starter-test starter will provide following libraries:
- JUnit
- Spring Test & Spring Boot Test
- AssertJ
- Hamcrest
- Mockito
- JSONassert
- JsonPath
application.properties
Let’s configure Spring Boot to use the embedded MongoDB instance instead of a separate MongoDB server.
de.flapdoodle.mongodb.embedded.version=5.0.5
spring.data.mongodb.database=test
spring.data.mongodb.port=0
- de.flapdoodle.mongodb.embedded.version: This property sets the version of the embedded MongoDB instance.
- spring.data.mongodb.database: Database name
- spring.data.mongodb.port: The port that Mongo listens on can be configured by setting the spring.data.mongodb.port property. To use a randomly allocated free port, use a value of 0.
Spring Data MongoDB– Student Document
package com.knf.dev.demo.document;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "students")
public class Student {
@Id
private String id;
private String name;
private String email;
private Integer age;
public Student() {
}
public Student(String id, String name, String email, Integer age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
- @Document is used to map a class to mongoDB database, it represents a MongoDB documents.
Spring Data MongoDB– Student Repository
It doesn't make sense to test inherited default methods like save(), findById(), deleteById(), or findAll() from MongoRepository. If we are doing like so, means we are testing the framework.
So, i created three methods findByName() , findByAgeGreaterThan() , and findByAgeLessThan().
package com.knf.dev.demo.repository;
import com.knf.dev.demo.document.Student;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import java.util.List;
import java.util.Optional;
public interface StudentRepository extends MongoRepository<Student, String> {
@Query("{name : ?0}")
Optional<Student> findByName(String name);
@Query("{ age : { $gte: ?0 } }")
List<Student> findByAgeGreaterThan(Integer age);
List<Student> findByAgeLessThan(Integer age);
}
- findByAgeGreaterThan(): If we want to retrieve students whose age is greater than the given age.
- findByName(): This method will get student entity by name.
- findByAgeLessThan(): If we want to retrieve students whose age is less than the given age.
- MongoRepository provides all the necessary methods which help to create a CRUD application and it also supports the custom derived query methods.
DataMongoTestExampleApplication.java
package com.knf.dev.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DataMongoTestExampleApplication {
public static void main(String[] args) {
SpringApplication.run(DataMongoTestExampleApplication.class, args);
}
}
Write unit test against embedded MongoDB
When using JUnit 4, this annotation should be used in combination with @RunWith(SpringRunner.class). But for this example we are using JUnit 5, there’s no need to add the equivalent @ExtendWith(SpringExtension.class).
package com.knf.dev.demo;
import com.knf.dev.demo.document.Student;
import com.knf.dev.demo.repository.StudentRepository;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
@DataMongoTest
public class StudentRepositoryTest {
// @Autowired
//private MongoTemplate mongoTemplate;
@Autowired
private StudentRepository studentRepository;
//Method should be executed before all tests in the current test class
//Load initial data
@BeforeAll
static void setup(@Autowired StudentRepository studentRepository) {
Student student1 = new Student("101","Alpha","alpha@knf.com",50);
Student student2 = new Student("102","Beta","beta@knf.com",40);
Student student3 = new Student("103","Gama","gama@knf.com",30);
Student student4 = new Student("104","Pekka","pekka@knf.com",20);
List<Student> students = Arrays.asList(student1,student2,student3,student4);
studentRepository.saveAll(students);
}
@Test
void findByName_ReturnsTheStudent() {
Student student = studentRepository.findByName("Alpha").get();
assertThat(student).isNotNull();
assertThat(student.getEmail()).isEqualTo("alpha@knf.com");
assertThat(student.getName()).isEqualTo("Alpha");
assertThat(student.getId()).isEqualTo("101");
assertThat(student.getAge()).isEqualTo(50);
}
@Test
void findByAgeGreaterThan_ReturnsTheListStudents() {
List<Student> students = studentRepository.findByAgeGreaterThan(29);
//Convert list of students to list of id(String)
List<String> ids = students.stream()
.map(o -> o.getId())
.collect(Collectors.toList());
assertThat(students.size()).isEqualTo(3);
assertThat(ids).hasSameElementsAs(Arrays.asList("103","102","101"));
}
@Test
void findByAgeLessThan_ReturnsTheListStudents() {
List<Student> students = studentRepository.findByAgeLessThan(31);
//Convert list of students to list of id(Integer)
List<String> ids = students.stream()
.map(o -> o.getId())
.collect(Collectors.toList());
assertThat(students.size()).isEqualTo(2);
assertThat(ids).hasSameElementsAs(Arrays.asList("104","103"));
}
}
- @BeforeAll is used to signal that the annotated method should be executed before all tests in the current test class.
- assertThat is used to check the specified value matches the expected value. It will accept the two parameters, the first contains the actual value, and the second will have the object matching the condition.
4. Run the test
Or you can run the test using following command:
mvn test -Dtest=StudentRepositoryTest