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 MongoTemplateIf 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

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete