MongoDB Atlas Local runs a local Atlas deployment in a single container. Next to MongoDB, it includes the Atlas Search process, so features like Atlas Search ($search) and Atlas Vector Search ($vectorSearch) can be tested without an Atlas cluster.
Add the following dependency to your project file:
NuGet
1
dotnetaddpackageTestcontainers.MongoDbAtlasLocal
You can start a MongoDB Atlas Local container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations. Authentication is disabled by default. Set a username and a password to enable it.
This example uses xUnit.net's IAsyncLifetime interface to manage the lifecycle of the container. The container is started in the InitializeAsync method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the DisposeAsync method.
namespaceTestcontainers.MongoDbAtlasLocal;internalstaticclassAtlasSearch{privatestaticreadonlyTimeSpanPollInterval=TimeSpan.FromMilliseconds(250);privatestaticreadonlyTimeSpanPollTimeout=TimeSpan.FromMinutes(1);publicstaticasyncTaskWaitUntilSearchIndexIsQueryableAsync(IMongoCollection<BsonDocument>collection,stringindexName,CancellationTokenct){// Atlas Search builds the index asynchronously, it becomes queryable shortly after it has been created.usingvartimeoutCts=CancellationTokenSource.CreateLinkedTokenSource(ct);timeoutCts.CancelAfter(PollTimeout);while(true){usingvarcursor=awaitcollection.SearchIndexes.ListAsync(indexName,cancellationToken:timeoutCts.Token).ConfigureAwait(false);varsearchIndexes=awaitcursor.ToListAsync(timeoutCts.Token).ConfigureAwait(false);if(searchIndexes.Any(searchIndex=>searchIndex.TryGetValue("queryable",outvarqueryable)&&queryable.ToBoolean())){return;}awaitTask.Delay(PollInterval,timeoutCts.Token).ConfigureAwait(false);}}publicstaticasyncTask<string[]>WaitUntilSearchReturnsAsync(IMongoCollection<BsonDocument>collection,stringindexName,SearchDefinition<BsonDocument>searchDefinition,intexpectedCount,CancellationTokenct){// mongot replicates documents from mongod asynchronously, a queryable index may not contain all documents yet.usingvartimeoutCts=CancellationTokenSource.CreateLinkedTokenSource(ct);timeoutCts.CancelAfter(PollTimeout);while(true){vardocuments=awaitcollection.Aggregate().Search(searchDefinition,indexName:indexName).ToListAsync(timeoutCts.Token).ConfigureAwait(false);if(documents.Count>=expectedCount){returndocuments.Select(document=>document["title"].AsString).ToArray();}awaitTask.Delay(PollInterval,timeoutCts.Token).ConfigureAwait(false);}}}
The test example uses the following NuGet dependencies:
To execute the tests, use the command dotnet test from a terminal.
Tip
For the complete source code of this example and additional information, please refer to our test projects.
Note
Atlas Search builds indexes asynchronously. A search index becomes queryable shortly after it has been created, and newly written documents become searchable shortly after they have been written. Wait until the index reports queryable: true before running search queries, and retry a query until it returns the documents you expect, as the Atlas Search helper above does.
Init scripts seed the deployment on its first start. WithInitScript(string) copies a script file from the test host, and WithInitScriptContent(string, string) creates one from a string. JavaScript (.js) scripts run in mongosh against the database set with WithInitDatabase(string) (default test), and shell (.sh) scripts run in bash. The container silently skips files with any other extension, so the builder rejects them. Scripts run in alphabetical order of their file names and finish before the container is reported ready. They can also create Atlas Search indexes. A restarted or reused container keeps its data and does not run the scripts again.
db.movies.insertMany([{title:"The Matrix"},{title:"Back to the Future"},{title:"The Matrix Reloaded"},]);db.movies.createSearchIndex("default",{mappings:{dynamic:true}});