loading...

Integration Testing in .NET Core 2.0

donbavand profile image Daniel Donbavand Updated on ・5 min read

This post was first published on my blog site

We all know how important writing tests are when developing software, it ensures that your code is working as expected and allows you to more easily refactor existing code. Tests can also help someone new to your application learn how it works and what functions its offers.

Each test level has its purpose, whether that be unit or integration testing. Having good integration tests are important when you are developing software. It ensures your application end to end flow is working correctly.

While making sure your project has well designed integration tests, it is equally important that your tests are easy to run and fast. This is an important point, if there is a big investment to set up in order for the tests to run, or the tests take a long time to run, people are simply not going to use them.

This blog post shows you how to create an in-memory integration testing framework that is quick and easy to setup for a .NET core 2.0 Web Service.

In-Memory Testing

Using an in-memory web host allows us to setup and run our tests quickly and easily. Kestrel is a cross-platform development web server that is used by .NET core applications.

Kestrel is a cross-platform web server for ASP.NET Core based on libuv, a cross-platform asynchronous I/O library. Kestrel is the web server that is included by default in ASP.NET Core project templates.
Kestrel supports the following features:

  • HTTPS
  • Opaque upgrade used to enable WebSockets
  • Unix sockets for high performance behind Nginx

Kestrel is supported on all platforms and versions that .NET Core supports.

Creating a simple .NET core 2.0 WebApi

Below I have setup a simple WebApi in .NET Core 2.0 with a single ping controller. This returns an OK response when called. I will create my integration test project inside this solution, then write and run my tests against the ping controller.

2017-08-29_6-47-19

    public class HealthcheckController : Controller
    {
        [HttpGet]
        [Route("ping")]
        public IActionResult Ping()
        {
            return Ok();
        }
    }

Setting up the Integration Test Framework

First let's create an integration test project inside our solution. I like to follow the following naming format ProjectName.Integration.Tests

Now we have our integration test project setup, we can start to create a test context, it is common for integration test classes to share setup and cleanup code we often call this a "test context". We will use the test context to setup a hosting framework ready to run our integration tests on. The test context for this example will be used to setup the test server and client.

2017-08-29_6-47-20

We now need to install the following nuget package Microsoft.AspNetCore.TestHost

ASP.NET Core includes a test host that can be added to integration test projects and used to host ASP.NET Core applications, serving test requests without the need for a real web host.

Once the Microsoft.AspNetCore.TestHost package is included in the project, you'll be able to create and configure a TestServer in your tests.

Once we have install the TestHost nuget package, we need to setup the test server and client.

 public class TestContext
    {
        private TestServer _server;
        public HttpClient Client { get; private set; }

        public TestContext()
        {
            SetUpClient();
        }

        private void SetUpClient()
        {
            _server = new TestServer(new WebHostBuilder()
                .UseStartup<Startup>());

            Client = _server.CreateClient();
        }
    }

Before starting to write our actual tests, we need to install a few nuget packages. xunit and xunit.runner.visualstudio I also like to use FluentAssertions, if you are following along, then install the fluentassertions package as well.

Fluent Assertions is a set of .NET extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style test.

Another important package you need to install is the Microsoft.NET.Test.Sdk otherwise your tests will show "Test Inconclusive" in the ‘Test Session Runner’ when trying to run them.

Here is a list of the nuget packages and versions I have installed. I'm including this screenshot as often versions change and this can cause problems, also the error messages can sometimes not be very helpful.

2017-08-31_6-09-08

The first test we are going to write is to test the ping controller. When we call the ping controller we should see an OK response. Very simple, but a good starting point to ensure your test framework and your WebApi are working correctly.

 public class PingTests
    {
        private readonly TestContext _sut;

        public PingTests()
        {
            _sut = new TestContext(); 
        }

        [Fact]
        public async Task PingReturnsOkResponse()
        {
            var response = await _sut.Client.GetAsync("/ping");

            response.EnsureSuccessStatusCode();

            response.StatusCode.Should().Be(HttpStatusCode.OK);
        }
    }   

That’s it, you now have an integration test framework, ready to add more tests to as you build out your WebApi.

Creating a Single Test Context

Creating tests with the structure above, creates a new test context per test scenario. This isn't always desirable, sometimes you want to setup your test context then run all tests or a collection of tests in your solution.

Setting up your test context once can have massive benefits, for example, if you need to deploy and publish a database as part of your integration test setup, doing this is going to take some time to complete. It might not make sense to setup the database for each test scenario. What would be a better plan, would be to set it up once, then run all your tests that interact with the database.

Xunit allows us to setup and create collections.

First we need to create a collection class. This class can be named whatever make sense to you.

[CollectionDefinition("SystemCollection")]
    public class Collection : ICollectionFixture<TestContext>
    {

    }

The collection class will never have any code inside it. The purpose of this class is to apply the [CollectionDefinition] decorator and all of the ICollectionFixture<> interfaces.

I have applied only one ICollectionFixture, but you can apply as many as you want.

Next add the IDisposable interface to your TestContext class, to ensure context cleanup happens.

  public class TestContext : IDisposable
    {
        private TestServer _server;
        public HttpClient Client { get; private set; }

        public TestContext()
        {
            SetUpClient();
        }

        private void SetUpClient()
        {
            _server = new TestServer(new WebHostBuilder()
                .UseStartup<Startup>());

            Client = _server.CreateClient();
        }

        public void Dispose()
        {
            _server?.Dispose();
            Client?.Dispose();
        }
    }

Now we add the CollectionDefinition name to the tests we want to run in a single collection

    [Collection("SystemCollection")]
    public class PingTests
    {

If the test class needs access to the fixture instance, add it as a constructor argument, and it will be provided automatically.

  [Collection("SystemCollection")]
    public class PingTests
    {
        public readonly TestContext Context;

        public PingTests(TestContext context)
        {
            Context = context;
        }

        [Fact]
        public async Task PingReturnsOkResponse()
        {
            var response = await Context.Client.GetAsync("/ping");

            response.EnsureSuccessStatusCode();

            response.StatusCode.Should().Be(HttpStatusCode.OK);
        }
    }

An important note is that when running tests in a collection they do not run in parallel. If you want your tests to run in parallel, then you need to either split out the collections or not use collections at all.

You can find the above code in my GitHub repository

Finally, if you want to discuss anything written here or in any of my other blog posts, you can find me on Twitter

Posted on by:

donbavand profile

Daniel Donbavand

@donbavand

Working at Xero as a Software Developer. Always looking for ways to improve, and people to share with.

Discussion

markdown guide
 

Nice post Don.

I'm learning ASP.NET Core WebAPI. This is a very clean and clear explanation on how to get started with integration testing. It's a very good starting point for anyone to follow. I love the step-by-step description.

 

Thanks, Jose, I'm pleased you are finding it useful.

 

Great, great example.

Places where I struggled: I didn't know I was to add a reference to the main WebAPI solution. If I didn't pay close attention to your screen cap showing that, I wouldn't have known. Maybe add a point for this?

What unit test solution template did you use? MSTest? xUnit? NUnit? There is no direction on how to create the unit test project. When you add a new project to your solution, you have to pick one of those three from VS2017 project selections. I chose xUnit since that is what you use further down in the article.

Also, if you have an appsettings.json that your WebAPI is dependent on, your tests won't run. To get around this, I did this:

test

 

Great Post Daniel.
I have a question. How can I use Selenium Test with this approach? Is there a method that can build the website for test browser integration?

Thanks.

 

Hi Leandro,

While I have used Selenium quite a bit, I haven't using this approach, so, unfortunately, I'm unable to offer much advice.

However, after having a quick search on the net, I did find this issue raised in aspnet/Hosting GitHub issues thread, that might help you get started.

github.com/aspnet/Hosting/issues/312

 

Hi Daniel.
I appreciate your exploring and bring me a possible solution.

Thanks

 

Awesome, this is such a clear and useful post. It was the 2nd result in my Google search and I'm so glad.

 

Hello,
How can i set ip address to my test server like a real server ?