Integration Testing with ASP.NET Core

Setting up integration testing is one of those things that you do once per project and then promptly forget. I usually have to look it up again each time, so instead I’m documenting it here, so that it’s easy to find in the future.

In this context, the integration tests will hit a real endpoint (using TestServer) and run all of the code in the API to serve the response, but mocking any external dependencies to the service (DB, Auth, etc.). I’ll walk through mocking out auth and custom services in a future article.

I’m going to skip to the end and do that first. Here’s how to setup the Test Project:

  1. Create a class library project
  2. Add the following references:
    • EntityFrameworkCoreMock.Moq
    • Microsoft.AspNetCore.TestHost
    • Microsoft.NET.Test.Sdk
    • Moq
    • xunit.assert
    • xunit.core
    • xunit.runner.visualstudio
    • TheProject
  3. Create a TestStartup file at the root with the following:
public class TestStartup {
    private DbContextMock<ProductContext> DbContextMock;

    public void ConfigureServices(IServiceCollection services) {
        services.AddAutoMapper(typeof(Startup));
        services.AddMediatR(typeof(Startup));

        services
            .AddHttpContextAccessor()
            .AddHttpClient();
        services.AddMvc()
            .AddApplicationPart(typeof(Startup).Assembly);

        this.DbContextMock = new DbContextMock<ProductContext>();
        services.AddSingleton(DbContextMock);
        services.AddSingleton(DbContextMock.Object);
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
        app.UseRouting();
        app.UseEndpoints(endpoints => {
            endpoints.MapControllers();
        });
    }
}

That should be all of the hard work done. A test looks like this now:

[Fact]
public async Task QueryByValidId_Returns200WithResult() {
    var id = Guid.NewGuid();
    var value = 0;

    using (var server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>()))
    using (var client = server.CreateClient())
    using (var scope = server.Services.CreateScope())
    {
        scope.ServiceProvider
            .GetRequiredService<DbContextMock<ProductContext>>()
            .CreateDbSetMock(
                db => db.DataPoints,
                new[] { new DataPoint() { Id = id, Value = value } });

        var request = new HttpRequestMessage(HttpMethod.Get, $"/api/datapoints/{id}");
        var response = await client.SendAsync(request);

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        var responseBody = await response.Content.ReadAsStringAsync();
        var responseObject = JsonConvert.DeserializeObject<DataPointDto>(responseBody);

        Assert.Equal(id, responseObject.Id);
        Assert.Equal(value, responseObject.Value);
    }
}

Protips:

  • Deserialize the response after asserting the status code… that way if your data contract isn’t correct, you’ll get the response code asserted before it throws serialization exception.
  • You can set the return value of CreateDbSetMock to be a variable and that will allow you to assert the contents of the “database” after the endpoint executes.

Code is here.

Hope it helps.

Configuring .NET Core Command Line Applications with Config Files

There’s a fair bit of documentation out there about using the new Configuration functionality in ASP.NET Core, but I had far more trouble finding something that was more applicable to a CommandLine .NET Core application than I thought I would, especially something that didn’t have anything to do with CommandLine arguments.

Effectively, I’d like to replicate the kind of behavior that I’m used to having with an app.config, using config transforms, with the settings available globally in my application. The only difference for my situation is that I don’t have a need to update the settings at runtime, so I’m not considering that. Either way, here’s how I’m doing it.

First, add the requisite NuGet packages:

  • Microsoft.Extensions.Configuration
  • Microsoft.Extensions.Configuration.Binder
  • Microsoft.Extensions.Configuration.FileExtensions
  • Microsoft.Extensions.Configuration.Json

Now add the following files to your solution:

Config.json

{
    "Config": {
        "MyNumberSetting" : 0, 
        "MyTextSetting" : "test"
    }
}

Config.Debug.json

{
    "Config": {
        "MyNumberSetting" : 1
    }
}

You can also have a Config.Release.json (or really, Config.[anything].json).

From here, I’ll use binding to have the configuration automatically deserialized into an object at startup. First things first: we need to make an object to hold the configuration settings. It should be named the same as the element in the config file that we want to deserialize, and the instance property names should match up as well. In this example, it looks like this:

public class Config {
    public int MyNumberSetting { get; set; }
    public string MyTextSetting { get; set; }
}

If you follow the docs, and examples online, you’ll find that the configuration binding will bind to an instance, but I said that I wanted the settings available globally. For that, this needs to be updated to:

public class Config {
    public int MyNumberSetting { 
        get { return MyNumber; }
        set { MyNumber = value; }
    }

    public string MyTextSetting { 
        get { return MyText; }
        set { MyText = value; }
    }

    public static int MyNumber { get; private set; }
    public static string MyText { get; private set; }
}

With this, when the config file is deserialized and mapped to an instance of this type, the instance setters will get hit and set the values of the static members. They have private setters, and so are mostly read only. You could still create a new instance, and call the setters, or use some reflection hijinks, but the private static member declares the intent well, and protects you from accidental attempts to update the value.

To load the settings, in Program.cs, I’m doing this on startup:

new ConfigurationBuilder()
    .SetBasePath(AppContext.BaseDirectory)
    .AddJsonFile("Config.json")
    .AddJsonFile("Config.Debug.json", optional: true)
//  .AddJsonFile("Config.[anything].json", optional: true)
    .Build()
    .GetSection("Config")
    .Bind(new Config());

That’s all that is needed to get global, (mostly) read only, application settings from configuration files in .NET Core. To access the values from anywhere, you can just do this:

int number = Config.MyNumber;
string text = Config.MyText;

Hope it helps!

Code available on GitHub.