Unleash Java MongoDB Atlas Search Testing with TestContainers: A Developer's Guide
Want to build amazing search features with MongoDB Atlas Search and Java? Local testing can be a pain, but TestContainers and JUnit5 offer a streamlined solution. This guide dives into using TestContainers for testing your Java MongoDB Atlas Search applications locally, ensuring consistent, reliable, and maintainable tests.
Why MongoDB Atlas Search Matters for Your Java Apps
MongoDB Atlas Search extends MongoDB's indexing capabilities with the power of Lucene, enabling:
- Full-text search capabilities: Build Google-like search experiences with ranked results, wildcard queries, and typo tolerance.
- Multi-index search: Overcome limitations of traditional MongoDB indexes by searching multiple indexes simultaneously for improved performance.
- Faceted search: Create Amazon.com-style category filters with counts for enhanced user interfaces.
- AI-powered search: Implement retrieval-augmented generation (RAG) using vector search.
MongoDB Atlas Search is a cloud-hosted service, but you can use a local instance for development and testing.
Local Development and Testing: Meet MongoDB Atlas Local and TestContainers
Testing is crucial when using MongoDB Atlas Search with Java. MongoDB offers a Docker container, MongoDB Atlas Local, to run mongot
and mongod
locally. Furthermore, TestContainers provides Java unit test support for MongoDB and MongoDB Atlas Local.
What is TestContainers?
TestContainers is a Java library that spins up Docker containers for integration testing. Its core benefits:
- Automated Container Management: Handles the lifecycle of containers within your unit tests.
- Environment Consistency: Runs reliably across different environments (local, CI/CD) with minimal configuration.
- Dynamic Port Allocation: Resolves port conflicts automatically and provides unique connection strings.
- Clean Test Environment: Cleans up after each test scope, ensuring a consistent state.
Getting Started: A Simple CRUD Example with Java and MongoDB
Let's start by creating a simple Java data access layer with basic CRUD operations for a Person
object:
Now, let's create JUnit5 tests with TestContainers:
Key points about the code:
@Testcontainers
&& @Container
Annotations: @Testcontainers
tells JUnit to look for @Container
annotations, which define the MongoDB Atlas Local container. TestContainers will manage the container's lifecycle, starting it before tests and stopping it afterward.
@AutoClose
Annotation: The @AutoClose
annotation on personDataAccess
ensures that the close()
method, which closes the MongoDB connection, is called after the tests.
- Standard JUnit Tests: The tests follow the Given/When/Then pattern for clear and maintainable assertions.
Testing MongoDB Atlas Search: Seeding Data and Waiting for Index Creation
Let's extend the PersonDataAccess
class with a findPersonByBio()
method for testing MongoDB Atlas Search. We'll also add seed data and a mechanism to ensure the index is created before running the tests.
First, add a stub method to PersonDataAccess Java class:
Then, create a new test class PersonDataAccessSearchTest
:
package com.mycodefu;
import com.mongodb.client.ListSearchIndexesIterable;
import com.mycodefu.PersonDataAccess.Person;
import org.bson.BsonDocument;
import org.bson.Document;
import org.junit.jupiter.api.AutoClose;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.mongodb.MongoDBAtlasLocalContainer;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
@Testcontainers
class PersonDataAccessSearchTest {
@Container
private static final MongoDBAtlasLocalContainer mongoDBContainer = new MongoDBAtlasLocalContainer("mongodb/mongodb-atlas-local:8.0.5");
@AutoClose
private static PersonDataAccess personDataAccess;
@BeforeAll
static void beforeAll() {
System.out.println("Initializing data access with MongoDB connection string: " + mongoDBContainer.getConnectionString());
personDataAccess = new PersonDataAccess(mongoDBContainer.getConnectionString());
personDataAccess.insertPerson(Person.of("Miss Scotty Leffler", 32, "farmer", "At 32, Miss Scotty Leffler is a dedicated farmer known for her innovative approaches to sustainable agriculture on her family-owned farm. Passionate about environmental stewardship, she combines traditional farming methods with modern technology to enhance crop yield and soil health."));
personDataAccess.insertPerson(Person.of("Raymon Wehner", 27, "dental hygienist", "At just 27 years old, Raymon Wehner is an accomplished dental hygienist dedicated to promoting oral health through comprehensive patient education and preventative care practices. With a passion for community outreach, Raymon frequently volunteers at local schools to teach children about the importance of maintaining good dental hygiene habits from an early age."));
personDataAccess.insertPerson(Person.of("Miss Steve Rempel", 22, "businessman", "At just 22 years old, Miss Steve Rempel has already made a significant mark as an innovative entrepreneur with a keen eye for emerging market trends and opportunities. Her dynamic approach to business is characterized by her ability to adapt quickly and lead diverse teams towards achieving ambitious goals, establishing herself as a rising star in the entrepreneurial landscape."));
personDataAccess.insertPerson(Person.of("Dustin Schinner", 45, "engineer", "At 45, Dustin Schinner is an accomplished engineer with over two decades of experience in innovative design and sustainable technology development. Known for his forward-thinking approach, he has led numerous successful projects that integrate cutting-edge solutions to address modern engineering challenges."));
personDataAccess.insertPerson(Person.of("Eartha Mosciski", 39, "window cleaner", "At 39, Eartha Mosciski has mastered the art of window cleaning, transforming ordinary buildings into sparkling showcases with her meticulous touch and eye for detail. Beyond just clearing away grime, she sees each pane as a canvas where light is artistically framed, bringing clarity and brightness to every view."));
personDataAccess.insertPerson(Person.of("Jackqueline Osinski", 23, "astronomer", "At just 23 years old, Jacqueline Osinski is making waves as an innovative astronomer dedicated to exploring the mysteries of distant galaxies. Her cutting-edge research on dark matter distribution has already earned her recognition in the scientific community and promises to reshape our understanding of the cosmos."));
personDataAccess.insertPerson(Person.of("Richard Ortiz II", 55, "lecturer", "Richard Ortiz II, at 55, is an esteemed lecturer renowned for his engaging teaching style and profound knowledge in his field of expertise. With years of experience shaping the minds of students across various disciplines, he continues to inspire through innovative educational approaches and a passion for lifelong learning."));
personDataAccess.insertPerson(Person.of("Brenton Bergstrom", 50, "bookkeeper", "At 50, Brenton Bergstrom is a seasoned bookkeeper with over two decades of experience ensuring the financial accuracy and integrity of businesses. Known for his meticulous attention to detail and dedication to precision, he plays a vital role in helping companies maintain their fiscal health and compliance."));
personDataAccess.insertPerson(Person.of("Carroll Ankunding", 39, "travel agent", "At 39, Carroll Ankunding is an e