danielwertheim

danielwertheim


notes from a passionate developer

Share


Sections


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

HttpRequester and in-memory Web-API testing

I’ve been working with ASP.Net WebAPI a bit lately. While doing this, I had the need for writing some integration tests by excercising the API using my little Requester library. I wanted a solution where I was doing requests against “hosted” API-endpoints, where the OWIN pipeline for the API has been executed and my Ninject container bootstraped, my filters for FluentValidation working… you get the picture right. To get this behaviour, I could have gone with in-process self-hosting, but found that the in-memory OWIN pipline cabable hosting solution to be a really nice fit (docs | NuGet). And after a tiny adjustment of Requester, it worked against it as well.

The code

More or less all the code for this post, exists in the GitHub repo for Requester. As it’s being tested against an in-memory hosted WebAPI.

The WebAPI

Lets say we have an ASP.Net OWIN enabled WebAPI, configured for hosting with IIS, from here-on and forward referred to as "TheWebAPI". This project of course has the Startup.cs that for this sample, also includes bootstrapping Ninject for use with WebAPI and OWIN.

namespace TheWebAPI
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var config = new HttpConfiguration();
            config.MapHttpAttributeRoutes();

            var kernel = CreateKernel();

            app.UseNinjectMiddleware(() => kernel)
               .UseNinjectWebApi(config);
        }

        private static StandardKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            kernel.Load(typeof(Startup).Assembly);
            return kernel;
        }
    }

    public class Dependencies : NinjectModule
    {
        public override void Load()
        {
            Kernel
                .Bind()
                .To()
                .InSingletonScope();
        }
    }
}

Lets put together a controller that we can use for this blog post.

namespace Requester.TestWebApi.Controllers
{
    [RoutePrefix("api/persons")]
    public class PersonController : ApiController
    {
        private readonly IPersonsStore _personsStore;

        public PersonController(IPersonsStore personsStore)
        {
            _personsStore = personsStore;
        }

        [HttpGet]
        [Route("{id}")]
        public HttpResponseMessage Get(Guid id)
        {
            var person = _personsStore.Get(id);
            if (person != null)
                return Request.CreateResponse(HttpStatusCode.OK, person);

            return Request.CreateResponse(HttpStatusCode.NotFound);
        }

        [HttpPut]
        [Route]
        public HttpResponseMessage Put(Person person)
        {
            _personsStore.Store(person);

            return Request.CreateResponse(HttpStatusCode.Created);
        }
    }

TheWebAPI.Tests

This project will make use of Requester, xUnit, the Microsoft.Owin.Testing and of course TheWepAPI. So:

install-package xUnit
install-package Requester
install-package Microsoft.Owin.Testing

After my inclussion of Ninject, it got some dependencies on OWIN parts, which forced me to install the following package in the test project:

install-package Microsoft.AspNet.WebApi.Owin

Now, add a project reference to TheWebAPI, which contains the Startup your controllers, routes etc.

Decision about shared context, configured external services etc.

Before proceeding, you need to reflect over at what “scope” you want your context to be defined. E.g. do you want a new in-memory TestServer to be created and disposed for each test? Or for all test in the class (fixture)? Or per collection? If you are sharing state between tests, and then e.g. do a replace of a resource in the bootstrapped Ninject container in one test, then that replacement will affect other tests executing after the test that replaced the resource. If you are using xUnit, then read this: “Shared Context between Tests”.

TestServer

Enough about contexts. For this case, we will make use of “per test context”, and since we are using xUnit, this is as simple as having a constructor and to implement IDisposable to clean stuff up.

namespace TheWebAPI.Tests
{
    public class InMemWebApiTests : IDisposable
    {
        private TestServer _server;

        public InMemWebApiTests()
        {
            //After this call, the Ninject kernel would
            //be initialized (if any)
            _server = TestServer.Create();

       //The specific TestServer message handler is
       //needed for use with the underlying HttpClient
            When.MessageHandlerFn = () => _server.Handler;
        }

        public void Dispose()
        {
            When.MessageHandlerFn = null;

            _server?.Dispose();
            _server = null;
        }

        //WRITE TESTS...
    }
}

We are good to go. Lets write some tests.

[Fact]
public void Sample_using_the_When_construct()
{
    var person = new Person
    {
        Id = Guid.NewGuid(),
        FirstName = "Daniel",
        LastName = "Wertheim",
        Age = 35
    };

    When.PutAsJson($"{_server.BaseAddress}/api/persons/", person)
        .TheResponse(should => should
            .BeSuccessful()
            .HaveStatus(HttpStatusCode.Created));

    When.GetOfJson(_server.BaseAddress + "api/persons/" + person.Id)
        .TheResponse(should => should
            .BeSuccessful()
            .BeJsonResponse()
            .HaveSpecificValue("FirstName", "Daniel"));
}

[Fact]
public async void Sample_using_the_HttpRequester()
{
    var person = new Person
    {
        Id = Guid.NewGuid(),
        FirstName = "Daniel",
        LastName = "Wertheim",
        Age = 35
    };

    using (var requester = new HttpRequester(
        $"{_server.BaseAddress}/api/persons/",
        _server.Handler))
    {
        var forThePut = await requester.PutEntityAsync(person);
        forThePut.TheResponse(should => should
            .BeSuccessful()
            .HaveStatus(HttpStatusCode.Created));

        var forTheGet = await requester.GetAsync(person.Id.ToString());
        forTheGet.TheResponse(should => should
            .BeSuccessful()
            .BeJsonResponse()
            .HaveSpecificValue("FirstName", "Daniel"));

        var getResponse = await requester.GetAsync(person.Id.ToString());
        var retrieved = getResponse.Content;
        retrieved.Should().NotBeNull();
        retrieved.FirstName.Should().Be("Daniel");
    }
}

The end

Nothing more to add really. If it helps you, then awesome! If not. Sorry. Any suggestions, pull requests etc to Requester is appreciated.

Thanks,

//Daniel

View Comments