Mongo2Go - MongoDB for .NET integration tests
đź‘‹ Do you need MongoDB 5 or even 6 for your testing or development needs? Check out EphemeralMongo, a project based on Mongo2Go. In addition to supporting multiple MongoDB major versions and the same features, it has a faster startup, and better CI support.
Mongo2Go is a managed wrapper around the latest MongoDB binaries. It targets .NET Standard 2.0 (and .NET 4.6 for legacy environments) and works with Windows, Linux and macOS. This Nuget package contains the executables of mongod, mongoimport and mongoexport for Windows, Linux and macOS .
Brought to you by Johannes Hoppe and CĂ©dric Luthi.
Mongo2Go has two use cases:
With each call of the static method MongoDbRunner.Start() a new MongoDB instance will be set up. A free port will be used (starting with port 27018) and a corresponding data directory will be created. The method returns an instance of MongoDbRunner, which implements IDisposable. As soon as the MongoDbRunner is disposed (or if the Finalizer is called by the GC), the wrapped MongoDB process will be killed and all data in the data directory will be deleted.
In this mode a single MongoDB instance will be started on the default port (27017). No data will be deleted and the MongoDB instance won’t be killed automatically. Multiple calls to MongoDbRunner.StartForDebugging() will return an instance with the State “AlreadyRunning”. You can ignore the IDisposable interface, as it won’t have any effect. I highly recommend to not use this mode on productive machines! Here you should set up a MongoDB as it is described in the manual. For you convenience the MongoDbRunner also exposes mongoexport and mongoimport which allow you to quickly set up a working environment.
MongoDbRunner.Start()
can be set up to take in an optional boolean parameter called singleNodeReplSet
.
When passed in with the value true
- (MongoDbRunner.Start(singleNodeReplSet: true)
)
singleNodeReplSet
.
Replica set mode is required for transactions to work in MongoDB 4.0 or greaterReplica set initialization requires the use of a short delay to allow for the replica set to stabilize. This delay is linked to a timeout value of 5 seconds.
If the timeout expires before the replica set has stabilized a TimeoutException
will be thrown.
The default timeout can be changed through the optional parameter singleNodeReplSetWaitTimeout
, which allows values between 0 and 65535 seconds: MongoDbRunner.Start(singleNodeReplSet: true, singleNodeReplSetWaitTimeout: 10)
MongoDbRunner.Start()
can be set up to consume additional mongod
arguments. This can be done using the string parameter called additionalMongodArguments
.
The list of additional arguments cannot contain arguments already defined internally by Mongo2Go. An ArgumentException
will be thrown in this case, specifying which additional arguments are required to be discarded.
Example of usage of the additional mongod
arguments: MongoDbRunner.Start(additionalMongodArguments: "--quiet")
The Mongo2Go Nuget package can be found at https://nuget.org/packages/Mongo2Go/
Search for „Mongo2Go“ in the Manage NuGet Packages dialog box or run:
PM> Install-Package Mongo2Go
or run for the deprecated .NET Standard 1.6 package:
PM> Install-Package Mongo2Go -Version 2.2.16
or run for the legacy .NET 4.6 package:
PM> Install-Package Mongo2Go -Version 1.1.0
in the Package Manager Console.
Example: Integration Test (here: Machine.Specifications & Fluent Assertions)
[Subject("Runner Integration Test")]
public class when_using_the_inbuild_serialization : MongoIntegrationTest
{
static TestDocument findResult;
Establish context = () =>
{
CreateConnection();
_collection.Insert(TestDocument.DummyData1());
};
Because of = () => findResult = _collection.FindOneAs<TestDocument>();
It should_return_a_result = () => findResult.ShouldNotBeNull();
It should_hava_expected_data = () => findResult.ShouldHave().AllPropertiesBut(d => d.Id).EqualTo(TestDocument.DummyData1());
Cleanup stuff = () => _runner.Dispose();
}
public class MongoIntegrationTest
{
internal static MongoDbRunner _runner;
internal static MongoCollection<TestDocument> _collection;
internal static void CreateConnection()
{
_runner = MongoDbRunner.Start();
MongoClient client = new MongoClient(_runner.ConnectionString);
MongoDatabase database = client.GetDatabase("IntegrationTest");
_collection = database.GetCollection<TestDocument>("TestCollection");
}
}
More tests can be found at https://github.com/Mongo2Go/Mongo2Go/tree/master/src/Mongo2GoTests/Runner
Example: Exporting seed data
using (MongoDbRunner runner = MongoDbRunner.StartForDebugging()) {
runner.Export("TestDatabase", "TestCollection", @"..\..\App_Data\test.json");
}
Example: Importing for local debugging (compatible with ASP.NET MVC 4 Web API as well as ASP.NET Core)
public class WebApiApplication : System.Web.HttpApplication
{
private MongoDbRunner _runner;
protected void Application_Start()
{
_runner = MongoDbRunner.StartForDebugging();
_runner.Import("TestDatabase", "TestCollection", @"..\..\App_Data\test.json", true);
MongoClient client = new MongoClient(_runner.ConnectionString);
MongoDatabase database = client.GetDatabase("TestDatabase");
MongoCollection<TestObject> collection = database.GetCollection<TestObject>("TestCollection");
/* happy coding! */
}
protected void Application_End()
{
_runner.Dispose();
}
}
Example: Transactions (New feature since v2.2.8)
public class when_transaction_completes : MongoTransactionTest
{
private static TestDocument mainDocument;
private static TestDocument dependentDocument;
Establish context = () =>
{
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
client = new MongoClient(_runner.ConnectionString);
database = client.GetDatabase(_databaseName);
_mainCollection = database.GetCollection<TestDocument>(_mainCollectionName);
_dependentCollection = database.GetCollection<TestDocument>(_dependentCollectionName);
_mainCollection.InsertOne(TestDocument.DummyData2());
_dependentCollection.InsertOne(TestDocument.DummyData2());
};
private Because of = () =>
{
var filter = Builders<TestDocument>.Filter.Where(x => x.IntTest == 23);
var update = Builders<TestDocument>.Update.Inc(i => i.IntTest, 10);
using (var sessionHandle = client.StartSession())
{
try
{
var i = 0;
while (i < 10)
{
try
{
i++;
sessionHandle.StartTransaction(new TransactionOptions(
readConcern: ReadConcern.Local,
writeConcern: WriteConcern.W1));
try
{
var first = _mainCollection.UpdateOne(sessionHandle, filter, update);
var second = _dependentCollection.UpdateOne(sessionHandle, filter, update);
}
catch (Exception e)
{
sessionHandle.AbortTransaction();
throw;
}
var j = 0;
while (j < 10)
{
try
{
j++;
sessionHandle.CommitTransaction();
break;
}
catch (MongoException e)
{
if (e.HasErrorLabel("UnknownTransactionCommitResult"))
continue;
throw;
}
}
break;
}
catch (MongoException e)
{
if (e.HasErrorLabel("TransientTransactionError"))
continue;
throw;
}
}
}
catch (Exception e)
{
//failed after multiple attempts so log and do what is appropriate in your case
}
}
mainDocument = _mainCollection.FindSync(Builders<TestDocument>.Filter.Empty).FirstOrDefault();
dependentDocument = _dependentCollection.FindSync(Builders<TestDocument>.Filter.Empty).FirstOrDefault();
};
It main_should_be_33 = () => mainDocument.IntTest.Should().Be(33);
It dependent_should_be_33 = () => dependentDocument.IntTest.Should().Be(33);
Cleanup cleanup = () => _runner.Dispose();
}
Example: Logging with ILogger
public class MongoIntegrationTest
{
internal static MongoDbRunner _runner;
internal static void CreateConnection()
{
// Create a custom logger.
// Replace this code with your own configuration of an ILogger.
var provider = new ServiceCollection()
.AddLogging(config =>
{
// Log to a simple console and to event logs.
config.AddSimpleConsole();
config.AddEventLog();
})
.BuildServiceProvider();
var logger = provider.GetSerivce<ILoggerFactory>().CreateLogger("Mongo2Go");
_runner = MongoDbRunner.Start(logger: logger);
}
}
public class MongoIntegrationTest
{
internal static MongoDbRunner _runner;
internal static void CreateConnection()
{
// Create a custom logger.
// Replace this code with your own configuration of an ILogger.
var provider = new ServiceCollection()
.AddLogging(config =>
{
// Mongod's D1-D2 levels are logged with Debug level.
// D3-D5 levels are logged with Trace level.
config.SetMinimumLevel(LogLevel.Trace);
// Log to System.Diagnostics.Debug and to the event source.
config.AddDebug();
config.AddEventSourceLogger();
})
.BuildServiceProvider();
var logger = provider.GetSerivce<ILoggerFactory>().CreateLogger("Mongo2Go");
_runner = MongoDbRunner.Start(
additionalMongodArguments: "vvvvv", // Tell mongod to output its D5 level logs
logger: logger);
}
}
dotnet pack
is now used to create the nupkg file for a release (PR #121 - many thanks to CĂ©dric Luthi)Microsoft.Extensions.Logging.ILogger
to MongoDbRunner.Start(logger)
arguments. Now you can adjust or disable the console output to avoid noise in CI environments. Please note the two examples shown above. (PR #113, fixes #94, #95 and #113 - many thanks to Corentin Altepe)--sslMode disabled
(deprecated) with --tlsMode disabled
in command line arguments to mongod.includes MongoDB binaries of version 4.4.4 with support for Windows, Linux and macOS
targets .NET Standard 2.1 (can be used with .NET Core 3.0 and .NET 5.0)
adds new MongoDownloader tool (PR #109, fixes #82 and #112 - many thanks to CĂ©dric Luthi)
adds support for NUGET_PACKAGES
environment variable (PR #110 - many thanks to Bastian Eicher)
singleNodeReplSetWaitTimeout
(PR #103 - many thanks for the continued support by José Mira)
sSystem.NotSupportedException : StartTransaction cannot determine if transactions are supported because there are no connected servers.
) (PR #101, fixes #89, #91 and #100 - many thanks to liangshiwei)Thread.Sleep(5000)
(PR #83, fixes #80 - many thanks again to José Mira)StartForDebugging()
(PR #72, fixes #71 - many thanks to Danny Bies)singleNodeReplSet
paramter to MongoDbRunner.Start
which allows mongod instance to be started as a replica set to enable transaction support (PR #57 - many thanks to Mahi Satyanarayana)MongoBinaryLocator
to look for binaries in the nuget cache if they are not found in the project directory.
binariesSearchDirectory
parameter to MongoDbRunner.Start
which allows an additional binaries search directory to be provided.
C:\data\db
by default (PR #42) - MongoDbRunner.Start()
and MongoDbRunner.StartForDebugging()
will now work without any extra parameters for Linux/macOSStart
and StartForDebugging
methods accept an optional parameter to specify a different data directory (default is "C:\data\db")Just fork the project, make your changes send us a PR.
You can compile the project with Visual Studio 2017 and/or the .NET Core 2.0 CLI!
In the root folder, just run:
dotnet restore
dotnet build
dotnet test src/Mongo2GoTests