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