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.

TeamCity on docker, building dotnet core and Angular

I wanted to setup build server for a personal project I’m working on, and I didn’t want to have any dependencies on the machine, so that I could move it easily. And, if I’m honest, the build server was a personal project as well.

Requirements

  • Build a dotnet core project
  • Build an angular project
  • No dependencies on the machine

Result

I set out to put together a docker-compose file that had everything in it that I needed. This is what I came up with.

Most of this is pretty straightforward.

  • A TeamCity container with log and data directories
  • A TeamCity Agent container that comes with dotnet build dependencies.
  • A TeamCity Agent container that comes with npm.
  • An environment variable (npm.installed) that is added to the npm agent. This will help later.
> docker-compose up -d

Setup

Navigate to http://localhost:8111

TeamCity will startup. Follow the directions to configure it using HSQLDB (we’ll use postgres another time) and create a user.

Configure an npm build (I referenced this excellent article).

Now do the following to make sure this build will only run on the correct agent(s):

  • Open your build configuration
  • Click > Edit Configuration Settings
  • Click > Agent Requirements
  • Click > Add New Requirement
  • Require that npm.installed exists as shown below

Now you should be able to run your build.

Hope it helps!