There are many advantages of testing with real databases over mocks. You can test real query flow, ensure that your results match what the database engine will do and avoid complicated and often fragile setup. The major disadvantage for wanting to avoid using a real database in tests is speed and complexity. Databases are inherently stateful, while you want to start each test as a new slate.
We assume that you are familiar with both RavenDB and unit testing in .NET in this article. We’ll cover how you can use RavenDB in your tests with ease, without giving up the rapid feedback that fast tests give you.
What you’ll learn
– How to avoid shortfalls of mocking the database
– How to integrate RavenDB.TestDriver
into your .NET project with no effort
– How to create integration tests that use a real, in-memory database, far more similar to production environment
Introduction
Mocking a database is a commonly used strategy for testing of database-driven applications. While mocks offer convenience and low effort, many bugs and erroneous program states might happen due to interactions with a database in a real production system. This discrepancy can lead to unexpected issues when transitioning from testing to deployment, ultimately undermining the effectiveness of the testing process.
It’s crucial to recognize that the effectiveness of your testing process heavily depends on how closely it mirrors real-life production conditions. Testing in an environment that is similar to production helps to detect potential issues early on, ensuring a smoother transition to deployment and a more reliable application in the hands of users.
Being aware of it, we’ve decided to provide a simple solution that developers may take leverage of to use the real database instead of mocks with no effort, while working with it in the various types of tests, especially in the integration tests. We’ve created test drivers, that let users instantiate the database component in no time.
We will be running RavenDB in-memory, ensuring that each test receives its own separated and isolated database instance. These instances will be automatically disposed, which solves the problem with a state of a database. Provisioning in-memory databases is fast and easy, making the testing seamlessly integrable into CI/CD pipelines. This approach not only simplifies the testing process but also ensures that developers can work with a database environment that closely mimics production conditions, leading to more reliable and accurate test results.
In this article we’ll take a closer look upon NuGet RavenDB.TestDriver package.
We’ll go quickly through the simple setup, to focus on the usage. Let’s dive into it.
Step-by-Step guide
What we’ll use
– C# Tests Framework: We’ll utilize the XUnit package for writing our test cases. It’s not mandatory, use your own favorite framework.
– RavenDB: We’ll integrate the RavenDB.TestDriver package to interact with RavenDB.
1. Installing RavenDB Test Driver
Begin by including the RavenDB.TestDriver
package in your project:
dotnet add package RavenDB.TestDriver
It will install the test driver and the embedded server.
2. Integrating Test Driver into your tests
Your test class like BasicTest
should be inheriting RavenTestDriver
from Raven.TestDriver
.
Step 2 – Creating a test case equipped with RavenTestDriver
public class BasicTest : RavenTestDriver
{
[Fact]
public void BasicTest()
{
If you prefer to avoid inheriting from RavenTestDriver
and use your own base class, this also work well with composition. You can just create an instance of the test driver in your tests. It is usually easier to just inherit the base class, so that it what we’ll show today.
You can override RavenTestDriver
methods to customize its behavior.
– The PreInitialize()
method is called right before the DocumentStore
is initialized. Pre-configure your DocumentStore
here, e.g. change the store DocumentConventions
– The SetupDatabase()
method is called right after the DocumentStore
is initialized. Here you can already work with a running server and store new documents, setup and configure features like revisions or expiration, prepare ongoing tasks like ETLs or subscriptions, execute various operations, etc.
Overriding RavenTestDriver PreInitialize() and SetupDatabase() methods
public class BasicTest : RavenTestDriver
{
protected override void PreInitialize(IDocumentStore documentStore)
{
documentStore.Conventions.FindCollectionName = t =>
{
if (t == typeof(Company))
return "BrewingCompanies";
return null;
};
}
protected override void SetupDatabase(IDocumentStore documentStore)
{
documentStore.Maintenance.Send(new ConfigureRevisionsOperation(new RevisionsConfiguration
{
Default = new RevisionsCollectionConfiguration
{
Disabled = false,
PurgeOnDelete = true,
MinimumRevisionsToKeep = 1,
MinimumRevisionAgeToKeep = TimeSpan.FromDays(14),
}
}));
}
[Fact]
public void BasicTest()
{
3. Using Test Driver to get database instance
Now, you can write new tests or replace mocks in existing tests with real database instances. Your document store IDocumentStore
is available by calling the GetDocumentStore()
method. It’s the one from RavenDB.Client
NuGet package, our .NET database client with powerful API. Interact with the docs and other entities in your database instance, like in the .NET C# client.
Step 3: Writing your test
public class BreweryTest : RavenTestDriver
{
[Fact]
public void TestBasicContractor()
{
using (var store = GetDocumentStore())
{
using (var session = store.OpenSession())
{
session.Store(new Contractor
{
FirstName = "Andy",
LastName = "Paulaner"
});
session.SaveChanges();
}
// the rest of your test
}
}
}
4. Debugging the database
In some cases, you might want to check what’s under the hood, working with the database. We’ve provided a WaitForUserToContinueTheTest(store)
method, that freezes the test and opens-up the Raven Studio UI in the background, which lets you investigate the issues and influence the databases/server state in the meantime. It works only when the debugger is attached, so you don’t have to add and remove the method call every time you want to run the code after debugging.
Step 4: Using ‘wait for user to continue the test’
public class BreweryTest : RavenTestDriver
{
[Fact]
public void TestBasicContractor()
{
using (var store = GetDocumentStore())
{
using (var session = store.OpenSession())
{
session.Store(new Contractor
{
FirstName = "Andy",
LastName = "Paulaner"
});
session.SaveChanges();
}
WaitForUserToContinueTheTest(store);
using (var session = store.OpenSession())
{
// Do something else if needed
}
// Assert
}
}
}
The studio pops out in the browser:

After you’re done reviewing your data in a database, click Continue test button located on the bottom UI bar, to continue the execution of a test.
5. Address indexing delays if needed
RavenDB works on indexing in the background. You can find more about it in the RavenDB docs on indexing process and stale indexes.
Usually, the delay from indexing is so small that it can be ignored. But it’s crucial during the tests.
In real life, programmers should know about this delay and how it can affect results. But here, unlike in real life, we use WaitForIndexing()
to ensure our tests won’t fail to interact with index which did not process the documents yet. We need to wait for indexes to finish to avoid test failures. Even single or couple of documents can cause a false-negative test result.
In the example below, we’re going a little bit further. We define the test driver that creates BreweryCustomersByShippingAddressLocation
spatial index (which can be queried later on) before every test using SetupDatabase() override. In the test we’ll insert the data, and then we’ll instruct the driver to wait for index to finish the processing.
Step 5. Instructing driver to wait for index to process the new data
public class CustomerGeoDoc
{
public string Name { get; set; }
public object ShippingLocation { get; set; }
}
public class BreweryCustomers_ByShippingAddressLocation : AbstractIndexCreationTask<CustomerGeoDoc>
{
public BreweryCustomers_ByShippingAddressLocation()
{
Map = customers => from c in customers
select new
{
Name = c.Name,
ShippingLocation = CreateSpatialField(c.ShippedTo.Lat, c.ShippedTo.Lng)
};
}
}
public class AdvancedBreweryTest : RavenTestDriver
{
protected override void SetupDatabase(IDocumentStore documentStore)
{
new BreweryCustomers_ByShippingAddressLocation().execute(documentStore);
}
[Fact]
public void TestCustomerDistributionStatistics()
{
using (var store = GetDocumentStore())
{
using (var session = store.OpenSession())
{
Customer customer1 = new Customer();
customer1.Name = "Paul Becks";
customer1.ShippedTo = new Location(51.509865, -0.118092);
Customer customer2 = new Customer();
customer2.Name = "Arjen Heineken";
customer2.ShippedTo = new Location(52.377956, 4.897070);
Customer customer3 = new Customer();
customer3.Name = "Tom Grodziski";
customer3.ShippedTo(new Location(52.105124, 20.591883);
session.Store(contractor1, "contractors/1");
session.Store(contractor2, "contractors/2");
session.Store(contractor3, "contractors/3");
session.SaveChanges();
}
// Let's wait for indexes to process the docs before asserting
WaitForIndexing(store);
}
// your code and assert
}
}
The WaitForIndexing(store)
method ensures that the database indexes are up-to-date before proceeding with the test. Stale indexes, which occur when queries are executed before indexing is complete, can lead to incorrect or outdated results in tests.
Registering a license
The RavenDB.TestDriver
package offers the option to register a license by providing an environmental variable called RAVEN_LICENSE
. While not mandatory, registering a license can unlock additional features and performance enhancements, particularly for advanced operations.
Enabling a license may increase the number of cores available, resulting in significant performance boosts for tests that heavily utilize the database. Consider registering a license for optimal performance in your testing environment.
To request a developer license, which is suitable for CI/CD purposes, visit the RavenDB license request page.
FAQ
Q: I’m getting this error after trying to run the test:
Unable to start the RavenDB Server
Error:
It was not possible to find any compatible framework version
The framework 'Microsoft.AspNetCore.App', version '5.0.13' (arm64) was not found.
- The following frameworks were found:
6.0.1 at [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
It says that dotnet framework installed on my machine isn’t compatible. Why the package doesn’t include dotnet included to actually run the server?
A: In order to maintain a lightweight package, we cannot include multiple framework versions with the server files. RavenDB typically aligns with the newest dotnet version on each release. It’s the responsibility of the user to ensure they have a compatible dotnet version installed. If you encounter errors like the one above, you can obtain the necessary dotnet version from the official dotnet website.
Conclusion
Integrating RavenDB into your .NETintegration tests using the RavenDB.TestDriver package offers a convenient way to move beyond mocking and interact with a real database instance. By following the steps outlined in this guide, you can seamlessly incorporate RavenDB into your testing workflow, leading to more robust and reliable tests.
The ability to work with real database instances provides a more accurate representation of your application’s behavior in a production environment, ultimately improving the quality of your software. Embrace the power of real database testing with RavenDB.TestDriver
and elevate your testing practices to new heights.