Getting Started: Writing your Unit Test using TestDriver


RavenTestDriver

Start by creating a class that derives from RavenTestDriver.
Find below a list of test driver methods, followed by samples.

TestDriver Methods

DatabaseDumpFilePath

Override the path to the database dump file that is loaded when calling ImportDatabase.

protected virtual string DatabaseDumpFilePath => null;

DatabaseDumpFileStream

Allow overriding the stream containing the database dump loaded when calling ImportDatabase.

protected virtual Stream DatabaseDumpFileStream => null;

GetDocumentStore

Get an IDocumentStore instance for the requested database.

public IDocumentStore GetDocumentStore([CallerMemberName] string database = null, 
                                                TimeSpan? waitForIndexingTimeout = null)

PreInitialize

Pre-initialize IDocumentStore.

protected virtual void PreInitialize(IDocumentStore documentStore)

PreConfigureDatabase

Pre configure the database record before creating it.

protected virtual void PreConfigureDatabase(DatabaseRecord databaseRecord)

SetupDatabase

Initialize the database

protected virtual void SetupDatabase(IDocumentStore documentStore)

DriverDisposed

An event raised when the test driver is disposed of

protected event EventHandler DriverDisposed;

ConfigureServer

Configure the server before running it

public static void ConfigureServer(TestServerOptions options)

WaitForIndexing

Wait for indexes to become non-stale

public void WaitForIndexing(IDocumentStore store, string database = null, 
                                                    TimeSpan? timeout = null)

WaitForUserToContinueTheTest

Pause the test and launch Studio to examine database state

public void WaitForUserToContinueTheTest(IDocumentStore store)

OpenBrowser

Open browser

protected virtual void OpenBrowser(string url)

Dispose

Dispose of the server

public virtual void Dispose()

Pre-initializing the store: PreInitialize

Pre-Initializing the IDocumentStore allows you to mutate the conventions used by the document store.

Example

//This allows us to modify the conventions of the store we get from 'GetDocumentStore'
protected override void PreInitialize(IDocumentStore documentStore)
{
    documentStore.Conventions.MaxNumberOfRequestsPerSession = 50;
}

Configure the server: ConfigureServer

The ConfigureServer method allows you to be more in control of your server.
You can use it with TestServerOptions to change the path to the Raven server binaries, specify data storage path, adjust .NET framework versions, etc.

  • ConfigureServer can only be set once per test run.
    It needs to be set before GetDocumentStore is called.
    See an example below.

  • If it is called twice, or within the DocumentStore scope, you will get the following error message: System.InvalidOperationException : Cannot configure server after it was started. Please call 'ConfigureServer' method before any 'GetDocumentStore' is called.

TestServerOptions

Defining TestServerOptions allows you to be more in control of how the embedded server is going to run with just a minor definition change.

  • To see the complete list of TestServerOptions, which inherits from embedded servers, go to embedded ServerOptions.
  • It's important to be sure that the correct .NET FrameworkVersion is set.

Example

var testServerOptions = new TestServerOptions
{
    // Looks for the newest version on your machine including 3.1.15 and any newer patches
    // but not major new releases (default is .NET version at time of server release).
    FrameworkVersion = "3.1.15+",

    // Specifies where ravendb server binaries are located (Optional)
    ServerDirectory = "PATH_TO_RAVENDB_SERVER",

    // Specifies where ravendb data will be placed/located (Optional)
    DataDirectory = "PATH_TO_RAVENDB_DATADIR", 
};

ConfigureServer(testServerOptions);

Unit test

We use xunit for the test framework in the below example.

Note that the test itself is meant to show different capabilities of the test driver and is not meant to be the most efficient.

The example below depends on the TestDocumentByName index and TestDocument class that can be seen in the full example

Example

In the example, we get an IDocumentStore object to our test database, deploy an index, and insert two documents into the document store.

We then use WaitForUserToContinueTheTest(store) which launches the Studio so we can verify that the documents and index are deployed (we can remove this line after the test succeeds).

Finally, we use session.Query to query for "TestDocument" where the name contains the word 'hello', and we assert that we have only one such document.

[Fact]
public void MyFirstTest()
{
    using (var store = GetDocumentStore())
    {
        store.ExecuteIndex(new TestDocumentByName());
        using (var session = store.OpenSession())
        {
            session.Store(new TestDocument { Name = "Hello world!" });
            session.Store(new TestDocument { Name = "Goodbye..." });
            session.SaveChanges();
        }
        // If we want to query documents, sometimes we need to wait for the indexes to catch up  
        // to prevent using stale indexes.
        WaitForIndexing(store);

        // Sometimes we want to debug the test itself. This method redirects us to the studio
        // so that we can see if the code worked as expected (in this case, created two documents).
        WaitForUserToContinueTheTest(store);

        using (var session = store.OpenSession())
        {
            var query = session.Query<TestDocument, TestDocumentByName>().Where(x => x.Name == "hello").ToList();
            Assert.Single(query);
        }
    }
}

Complete example

This is a full unit test using Xunit.

In the test, we get an IDocumentStore object to our test database, deploy an index, and insert two documents into the document store.

We then use WaitForUserToContinueTheTest(store) which launches the Studio so we can verify that the documents and index are deployed (we can remove this line after the test succeeds).

Finally, we use session.Query to query for "TestDocument" where the name contains the word 'hello', and we assert that we have only one such document.

using Raven.Client.Documents;
using Raven.TestDriver;
using Xunit;
using System.Linq;
using Raven.Client.Documents.Indexes;

namespace RavenDBTestDriverFullExample
{

    public class RavenDBTestDriver : RavenTestDriver
    {
        public RavenDBTestDriver()
        {
            // ConfigureServer() must be set before calling GetDocumentStore()
            // and can only be set once per test run.
            ConfigureServer(new TestServerOptions
            {
                DataDirectory = "C:\\RavenDBTestDir"
            });
        }
        // This allows us to modify the conventions of the store we get from 'GetDocumentStore'
        protected override void PreInitialize(IDocumentStore documentStore)
        {
            documentStore.Conventions.MaxNumberOfRequestsPerSession = 50;
        }

        [Fact]
        public void MyFirstTest()
        {
            // GetDocumentStore() evokes the Document Store, which establishes and manages communication
            // between your client application and a RavenDB cluster via HTTP requests.
            using (var store = GetDocumentStore())
            {
                store.ExecuteIndex(new TestDocumentByName());
                using (var session = store.OpenSession())
                {
                    session.Store(new TestDocument { Name = "Hello world!" });
                    session.Store(new TestDocument { Name = "Goodbye..." });
                    session.SaveChanges();
                }
                // If we want to query documents, sometimes we need to wait for the indexes to catch up  
                // to prevent using stale indexes.
                WaitForIndexing(store);

                // Sometimes we want to debug the test itself. This method redirects us to the studio
                // so that we can see if the code worked as expected (in this case, created two documents).
                WaitForUserToContinueTheTest(store);

                // Queries are defined in the session scope.
                // If there is no relevant index to quickly answer the query, RavenDB creates an auto-index
                // based on the query parameters.
                // This query will use the static index defined in lines 63-70 and filter the results by name.
                using (var session = store.OpenSession())
                {
                    var query = session.Query<TestDocument, TestDocumentByName>()
                        .Where(x => x.Name == "hello").ToList();
                    Assert.Single(query);
                }
            }
        }
    }
    // AbstractIndexCreationTask allows you to create and manually define a static index. 
    public class TestDocumentByName : AbstractIndexCreationTask<TestDocument>
    {
        public TestDocumentByName()
        {
            Map = docs => from doc in docs select new { doc.Name };
            Indexes.Add(x => x.Name, FieldIndexing.Search);
        }
    }

    public class TestDocument
    {
        public string Name { get; set; }
    }
}

Continuous Integration (CI) Servers

Best practice is to use a CI/CD server to help automate the testing and deployment of your new code. Popular CI/CD products are AppVeyor or Visual Studio Team Services (aka. VSTS).

Licensing

The embedded server that TestDriver uses while running your tests can only apply the features and access the resources defined by its license.
An unlicensed server, for example, will be able to use no more than 3 CPU cores, while a server licensed using a free developers license will be able to use up to 9 cores and run way faster.

  • When the server is started, its license is validated.

    • If the validation succeeds, the server will run, applying the capabilities defined by its license.
    • If the validation fails, the server may still run - but its capabilities will be limited to those defined by the basic AGPL license (e.g., using up to 3 CPU cores).

      If the validation fails because the license expired, and the expiration date precedes the server build date, the server will not run.

  • A TestServerOptions.Licensing.ThrowOnInvalidOrMissingLicense configuration option is available since RavenDB 5.4, determining whether to throw a LicenseExpiredException exception if TestDriver uses an unlicensed embedded server.

    • If ThrowOnInvalidOrMissingLicense is set to true and the validation fails, a LicenseExpiredException exception will be thrown to warn TestDriver users that in lack of a valid license, their server's capabilities are limited and they may therefore miss out on much of their system's potential.
    • If the configuration option is set to false, no exception will be thrown even if a license cannot be validated.
    • TestServerOptions.Licensing.ThrowOnInvalidOrMissingLicense is set by default to true since RavenDB version 6.2, so a LicenseExpiredException exception would be thrown if the embedded server used by TestDriver fails to validate a license.
  • Additional TestServerOptions.Licensing configuration options are available as well, you can read about them here.