StackTips
 10 minutes

Testing Spring Boot Repository Using MongoDB Testcontainers

By Nilanchala @nilan, On Mar 24, 2024 Spring Boot 906 Views

Testcontainers are the lightweight, throwaway container instances, used for common test cases like testing against a database, ActiveMQ or anything else that can run in a Docker container.

Testcontainers allows you to run your tests against a real instance of your application's dependencies, such as a real database, rather than against a mock or an in-memory database. We can configure the Testcontainers to closely mimic the production environment by using the specific versions of your your dependencies and pre-load with test data .

It spins up a new container instance for each tests, hence every tests are completely isolated from each other.

For running tests using the Testcontainers, we need to have the docker is up and running, no other dependencies are required.

Let us see the following Repository,

We have defined a searchMovies() method that uses MongoTemplate to perform search by different search criteria's.

@Repository  
public class MovieRepository {  

    private final MongoTemplate mongoTemplate;  

    public MovieRepository(MongoTemplate mongoTemplate) {  
        this.mongoTemplate = mongoTemplate;  
    }  

    public List<Movie> searchMovies(SearchRequest searchRequest) {  
        Query query = new Query();  
        if (null != searchRequest.rating()) {  
            query.addCriteria(Criteria.where("rating")
                .is(searchRequest.rating()));  
        }  

        if (null != searchRequest.language()) {  
            query.addCriteria(Criteria.where("language")
                .is(searchRequest.language()));  
        }  

        if (null != searchRequest.genre()) {  
            query.addCriteria(Criteria.where("genres")
                .is(searchRequest.genre()));  
        }  

        return mongoTemplate.find(query, Movie.class);  
    }  
}

Add Testcontainers Dependency

Before we begin, we need to ensure we have the necessary test container dependency added to your pom.xml or build.gradle file.

testImplementation 'org.springframework.boot:spring-boot-testcontainers'  
testImplementation "org.testcontainers:junit-jupiter:1.19.6"  
testImplementation "org.testcontainers:mongodb:1.19.6"

Configure Testcontainer

Create a test configuration class that starts a MongoDB container. This will be used for testing our Repository that uses MongoTemplate for performing MongoDB operations.

@Testcontainers
@DataMongoTest(includeFilters = @ComponentScan.Filter(Repository.class))
class MovieRepositoryTest {

    @Autowired  
    MovieRepository repository;
    @Autowired  
    MongoTemplate mongoTemplate;

    @Container  
    static final MongoDBContainer mongoDbContainer = new MongoDBContainer("mongo:latest")
            .withExposedPorts(27017);


    @DynamicPropertySource
    static void setProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.mongodb.host", mongoDbContainer::getHost);
        registry.add("spring.data.mongodb.port", mongoDbContainer::getFirstMappedPort);
        registry.add("spring.data.mongodb.username", () -> "test_user");
        registry.add("spring.data.mongodb.password", () -> "test_password");
        registry.add("spring.data.mongodb.database", () -> "movies_db");
        registry.add("spring.data.mongodb.uri", mongoDbContainer::getReplicaSetUrl);
    }

    static {
        mongoDbContainer.start();
    } 
}

Preload MongoDB Testcontainer with Test Data

We can preload the MongoDB Testcontainer with test data. To do that we need to create an initialization script in /test/resources directory. Lets call it init-schema.js.

init-schema.js

db = db.getSiblingDB('movies_db');  

db.movies.insertMany([  
    {  
        "title": "Iron Man & Captain America: Heroes United",  
        "headline": "Iron Man (Adrian Pasdar) and Captain America ...",  
        "thumbnail": "https://flxt.tmsimg.com/assets/p10906420_v_h9_aa.jpg",  
        "language": "EN",  
        "region": "USA",  
        "actors": [  
            "David Kaye",  
            "Ian McKellen",  
            "Adrian Pasdar"  
        ],  
        "genre": "Adventure",  
        "rating": "G",  
    },  
    {  
        "title": "Transformers: Rise of the Beasts",  
        "headline": "Transformers: Rise of the Beasts will take audiences on a",  
        "thumbnail": "https://flxt.tmsimg.com/assets/p20201199_v_h9_am.jpg",  
        "language": "EN",
        "region": "USA",
        "actors": [
            "David Kaye",
            "Ian McKellen",
            "Adrian Pasdar"
        ],
        "genres": "Action",
        "rating": "G"
    },    
]);

For init-schema.js to work, we need to use withCopyFileToContainer() method. The withCopyFileToContainer() method allows moving the initialization script to a location inside the container.

@Container  
static final MongoDBContainer mongoDbContainer = new MongoDBContainer("mongo:latest")  
        .withExposedPorts(27017)  
        .withCopyFileToContainer(MountableFile.forClasspathResource("./init-schema.js"),  
                "/docker-entrypoint-initdb.d/init-script.js");

Testing Repository using Testcontainer

Now that we have setup the test container and our initialization script is ready, let us now use the Testcontainer instance to write some unit tests.

Here is how our MovieRepositoryTest class look like:

import com.stacktips.movies.dto.SearchRequest;
import com.stacktips.movies.models.ContentRating;
import com.stacktips.movies.models.Movie;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.mongodb.core.MongoTemplate; 
import org.springframework.stereotype.Repository;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;

import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

@Testcontainers
@DataMongoTest(includeFilters = @ComponentScan.Filter(Repository.class))
class MovieRepositoryTest {

    @Autowired  
    MovieRepository repository;
    @Autowired  
    MongoTemplate mongoTemplate;

    @Container  
    static final MongoDBContainer mongoDbContainer = new MongoDBContainer("mongo:latest")
            .withExposedPorts(27017)
            .withCopyFileToContainer(MountableFile.forClasspathResource("./init-schema.js"),
                    "/docker-entrypoint-initdb.d/init-script.js");

    @DynamicPropertySource
    static void setProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.mongodb.host", mongoDbContainer::getHost);
        registry.add("spring.data.mongodb.port", mongoDbContainer::getFirstMappedPort);
        registry.add("spring.data.mongodb.username", () -> "test_user");
        registry.add("spring.data.mongodb.password", () -> "test_password");
        registry.add("spring.data.mongodb.database", () -> "movies_db");
        registry.add("spring.data.mongodb.uri", mongoDbContainer::getReplicaSetUrl);
    }

    static {
        mongoDbContainer.start();
    }

    @Test  
    void testMoviesCount() {
        List<Movie> movies = mongoTemplate.findAll(Movie.class);
        assertThat(4, is(movies.size()));
    }

    @Test  
    void testSearchMovies() {  
        SearchRequest searchRequest = new SearchRequest(ContentRating.G, "EN", "Action");
        List<Movie> movies = repository.searchMovies(searchRequest);
        assertThat(2, is(movies.size()));
        assertThat(movies.get(0).title(), is("Transformers: Rise of the Beasts"));
        assertThat(movies.get(0).language(), is("EN"));
        assertThat(movies.get(0).region(), is("USA")); 
        assertThat(movies.get(0).rating(), is(ContentRating.valueOf("G")));
    }
}

For the complete project source code check out the download link.

If you have any questions, write down in the comment section below. Will be happy to respond, soon as I can.

nilan avtar

Nilanchala

I'm a blogger, educator and a full stack developer. Mainly focused on Java, Spring and Micro-service architecture. I love to learn, code, make and break things.