C# / .NETDevOpsMisc
C# / .NET
Using RavenDB in Integration Testing
Alexandru Puiu
Alexandru Puiu
December 24, 2022
2 min

Table Of Contents

01
Set up
02
Prepare to deal with Authentication
03
Setting up our Web Application Factory
04
Test Fetching Data from an API

Testing REST APIs in some cases is tricky, because the role of some endpoints is just to pass data around or transform it slightly, so Mock/Unit tests don’t necessarily make sense sometimes. Here we’ll look at a way to set up an in-memory RavenDB document store, start up our API pointing to our in-memory document store, then in the test, add data directly to our database, then interact with it via our API, and finally check the data’s state in our document store.

Set up

XUnit is the testing framework preferred by Microsoft, and comes with a lot of utils. First, let’s install the nuget packages we’ll be using.

XUnit and the runners we’ll be using

Install-Package xunit
Install-Package xunit.runner.console
Install-Package xunit.runner.visualstudio
Install-Package Microsoft.NET.Test.Sdk

Helpers and the Moq framework

Install-Package Microsoft.AspNetCore.Mvc.Testing
Install-Package Microsoft.TestPlatform.TestHost
Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables
Install-Package Moq

The RavenDB Test Driver and in-memory store

Install-Package RavenDB.TestDriver

Logging utils

Install-Package Divergic.Logging.Xunit
Install-Package tsh.xunit.logging

Code coverage collector

Install-Package coverlet.collector

Prepare to deal with Authentication

Our API likely has authentication. In this case we’ll explore Cookie authentication, but Bearer authentication should work the same. We’ll define our Mock Autehntication handler that will replace our Cookie scheme. Here you can set all the claims your application expects of a fully logged in user.

public class MockAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public MockAuthenticationHandler(
        IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock
    )
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] {
            new Claim(JwtClaimTypes.Subject, "ApplicationUsers/1-A"),
            new Claim(JwtClaimTypes.Name, "Test User"),
            new Claim(JwtClaimTypes.Email, "[email protected]")
        };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "Test");

        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}

public class MockSchemeProvider : AuthenticationSchemeProvider
{
    public MockSchemeProvider(IOptions<AuthenticationOptions> options)
        : base(options)
    {
    }

    protected MockSchemeProvider(
        IOptions<AuthenticationOptions> options,
        IDictionary<string, AuthenticationScheme> schemes
    )
        : base(options, schemes)
    {
    }

    public override Task<AuthenticationScheme> GetSchemeAsync(string name)
    {
        if (name == "Cookies")
        {
            var scheme = new AuthenticationScheme(
                "Cookies",
                "Cookies",
                typeof(MockAuthenticationHandler)
            );
            return Task.FromResult(scheme);
        }

        return base.GetSchemeAsync(name);
    }
}

Setting up our Web Application Factory

Now we’re ready to set up our application host for testing. We’ll extend WebApplicationFactory with our own class where we can configure defaults for all tests

public class MyWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override void ConfigureWebHost(IWebHostBuilder hostBuilder)
    {
        // add any configuration items our application expects
        var myConfiguration = new Dictionary<string, string?>
        {
            {"LogStorage:Loki:Url", "https://test"},
            {"LogStorage:AzureStorage", " UseDevelopmentStorage=true;DevelopmentStorageProxyUri=https://127.0.0.1"},
            {"Authentication:Authority", "https://test" },
            {"Authentication:ClientId", "test" },
            {"Authentication:ClientSecret", "test" },
            {"Redis", null }
        };

        hostBuilder.ConfigureAppConfiguration((ctx, builder) => builder.AddInMemoryCollection(myConfiguration));

        // name our environment Test
        hostBuilder.UseEnvironment("Test")
            .ConfigureServices(services =>
            {

            })
            .ConfigureTestServices(services =>
            {
                // Remove services that might interfere with our testing
                services.RemoveAll(typeof(IHealthCheck));
                
                // Inject our Cookie scheme provider, which will take precedence over the one configured in our Startup.cs
                services.AddTransient<IAuthenticationSchemeProvider, MockSchemeProvider>();
            })
            .ConfigureLogging(logging =>
            {
                logging.ClearProviders();
                logging.SetMinimumLevel(LogLevel.Trace);
            });

        base.ConfigureWebHost(hostBuilder);
    }
}

Now we’re ready to move on to our tests. We’ll need to inherit from RavenTestDriver and get MyWebApplicationFactory as a class fixture. The class fixture injects our web application factory via the constructor. The GetDocumentStore method comes from the RavenTestDriver and gets us a client to a newly created in memory database, and we’ll override the config to point to that instance.

public class AccountControllerTests : RavenTestDriver, IClassFixture<MyWebApplicationFactory<Startup>>
{
    private readonly HttpClient _client;
    private IDocumentStore _store;

    public AccountControllerTests(MyWebApplicationFactory<Startup> factory)
    {
        _store = GetDocumentStore();

        var myConfiguration = new Dictionary<string, string?>
        {
            {"Raven:Urls:0", _store.Urls[0] },
            {"Raven:DatabaseName", _store.Database }
        };
        
        _client = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureAppConfiguration((ctx, builder) => builder.AddInMemoryCollection(myConfiguration));
        }).CreateClient();
    }
    ...
}

I find it useful to get some more feedback in tests, so I typically inject an ITestOutputHelper and add it as a logging provider in the API. This way I can capture log statements made during testing, and I can write output during the test if something goes wrong. So our class becomes

public class AccountControllerTests : RavenTestDriver, IClassFixture<MyWebApplicationFactory<Startup>>
{
    private readonly ITestOutputHelper _output;
    private readonly HttpClient _client;
    private IDocumentStore _store;

    public AccountControllerTests(MyWebApplicationFactory<Startup> factory, ITestOutputHelper output)
    {
        _output = output;
        _store = GetDocumentStore();

        var myConfiguration = new Dictionary<string, string?>
        {
            {"Raven:Urls:0", _store.Urls[0] },
            {"Raven:DatabaseName", _store.Database }
        };

        _client = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureLogging(lb => lb.AddProvider(new XUnitLoggerProvider(output)));
            builder.ConfigureAppConfiguration((ctx, builder) => builder.AddInMemoryCollection(myConfiguration));
        }).CreateClient();
    }

Since the API I’m testing uses IHttpClientFactory to make outbound calls, I also set up for that, but this is optional, but I find it to be very useful

public class AccountControllerTests : RavenTestDriver, IClassFixture<MyWebApplicationFactory<Startup>>
{
    private readonly ITestOutputHelper _output;
    private readonly HttpClient _client;
    private IDocumentStore _store;

    public AccountControllerTests(MyWebApplicationFactory<Startup> factory, ITestOutputHelper output)
    {
        _output = output;
        _store = GetDocumentStore();

        var myConfiguration = new Dictionary<string, string?>
        {
            {"Raven:Urls:0", _store.Urls[0] },
            {"Raven:DatabaseName", _store.Database }
        };

        var mockClientFactory = new Mock<IHttpClientFactory>();

        var handlerMock = new Mock<HttpMessageHandler>();
        handlerMock
            .Protected()
            // Setup the PROTECTED method to mock
            .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
            // prepare the expected response of the mocked http call
            .ReturnsAsync(new HttpResponseMessage()
            {
                StatusCode = HttpStatusCode.OK
            })
            .Verifiable();

        // use real http client with mocked handler here
        var httpClient = new HttpClient(handlerMock.Object)
        {
            BaseAddress = new Uri("https://scatteredcode.net/"),
        };

        mockClientFactory.Setup(t => t.CreateClient(It.IsAny<string>())).Returns(httpClient);

        _client = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureLogging(lb => lb.AddProvider(new XUnitLoggerProvider(output)));
            builder.ConfigureAppConfiguration((ctx, builder) => builder.AddInMemoryCollection(myConfiguration));
            builder.ConfigureServices(services =>
            {
                services.AddSingleton(mockClientFactory.Object);
            });
        }).CreateClient();
    }

Now we have everything we need to write our tests

Test Fetching Data from an API

For reference, this is the API method we’ll be testing

[Route("whoami")]
public async Task<IActionResult> Whoami(CancellationToken ct = default)
{
    using (var session = _store.OpenAsyncSession())
    {
        var userId = User.FindFirstValue("sub");
        var user = await session.LoadAsync<ApplicationUser>(userId, ct);

        return Ok(new WhoAmIModel
        {
            FirstName = user.FirstName,
            LastName = user.LastName,
            Email = user.Email,
            Role = user.Role
        });
    }
}

First we’ll add the data we expect to find in the database

var user = new ApplicationUser
{
    Id = "Users/1",
    AccountStatus = "Active",
    FirstName = "First Name",
    LastName = "Last Name",
    Email = "[email protected]"
};

var team = new Team
{
    Id = "Teams/Team1",
    Name = "Team 1"
};

using (var session = _store.OpenAsyncSession())
{
    await session.StoreAsync(user);
    await session.StoreAsync(team);
    await session.SaveChangesAsync();
}

Make a call to our API method expecting to receive this data

var response = await _client.GetAsync("whoami");

response.EnsureSuccessStatusCode();

var userInfo = await response.Content.ReadFromJsonAsync<WhoAmIModel>();

Assert our results

Assert.NotNull(userInfo);
Assert.Equal(user.FirstName, userInfo.FirstName);
Assert.Equal(user.LastName, userInfo.LastName);
Assert.Equal(user.Email, userInfo.Email);
Assert.Equal(user.Role, userInfo.Role);

Our full test

[Fact(DisplayName = "Who am i returns correct user info")]
public async Task WhoAmI()
{
    var user = new ApplicationUser
    {
        Id = "Users/1",
        AccountStatus = "Active",
        FirstName = "First Name",
        LastName = "Last Name",
        Email = "[email protected]"
    };

    var team = new Team
    {
        Id = "Teams/Team1",
        Name = "Team 1"
    };

    using (var session = _store.OpenAsyncSession())
    {
        await session.StoreAsync(user);
        await session.StoreAsync(team);
        await session.SaveChangesAsync();
    }

    var response = await _client.GetAsync("whoami");

    response.EnsureSuccessStatusCode();

    var userInfo = await response.Content.ReadFromJsonAsync<WhoAmIModel>();

    Assert.NotNull(userInfo);
    Assert.Equal(user.FirstName, userInfo.FirstName);
    Assert.Equal(user.LastName, userInfo.LastName);
    Assert.Equal(user.Email, userInfo.Email);
    Assert.Equal(user.Role, userInfo.Role);
}

Tags

utilstesting
Alexandru Puiu

Alexandru Puiu

Engineer / Security Architect

Systems Engineering advocate, Software Engineer, Security Architect / Researcher, SQL/NoSQL DBA, and Certified Scrum Master with a passion for Distributed Systems, AI and IoT..

Expertise

.NET
RavenDB
Kubernetes

Social Media

githubtwitterwebsite

Related Posts

Authentication in HttpClientFactory
Authentication in Http Client Factory
December 21, 2022
1 min

Subscribe To My Newsletter

I'll only send worthwhile content I think you'll want, less than once a month, and promise to never spam or sell your information!
© 2023, All Rights Reserved.

Quick Links

Get In TouchAbout Me

Social Media