Alba 3.0 – TestServer on steroids and HTTP contract testing for .Net

Hey all, I was just able to make a 3.0 release of Alba with quite a few improvements. As introduced a couple years ago, Alba is a library you can use in combination with xUnit/NUnit to much more easily author integration tests against ASP.Net Core applications by running HTTP requests in process.

Some salient things:

Big Changes:

  • The core Alba Nuget package supports ASP.Net Core v2.1 and v2.2. Likewise, I’ve tested Alba with netcoreapp2.0 and netcoreapp2.1
  • The Alba.AspNetCore2 nuget has been deprecated
  • All support for ASP.Net Core < 2.0 and targets < netstandard2.0 have been eliminated
  • The latest Alba uses the ASP.Net Core TestServer under the covers. When I first released Alba I got plenty of “why didn’t you just use TestServer?”. So A.) now Alba does and that’s less code we have to support and much less weird incompatibilities to worry about, so yay! B.) Alba provides a lot of value and removes a lot of repetitive grunt work you’d have to deal with if you only used TestServer as is.
  • I added some syntactical sugar for common usages of “I only need to send and receive JSON to and from .Net objects” you can see in the new Getting Started tutorial

Do note that the advent of the Microsoft.AspNetCore.All metapackage makes it unfortunately likely that you’ll bump into assembly binding conflicts at runtime by consuming libraries like Alba. There are tips in the same Getting Started page for preventing or fixing these issues as they arise when you try to use Alba in your tests.

Show me a sample!

Hey, it’s silly, but let’s say you’ve got a Controller in your ASP.Net Core system that looks like this (with its related message DTO types):

    public enum OperationType
    {
        Add,
        Subtract,
        Multiply,
        Divide
    }
    
    public class OperationRequest
    {
        public OperationType Type { get; set; }
        public int One { get; set; }
        public int Two { get; set; }
    }

    public class OperationResult
    {
        public int Answer { get; set; }
        public string Method { get; set; }
    }
    
    
    public class MathController : Controller
    {
        [HttpGet("/math/add/{one}/{two}")]
        public OperationResult Add(int one, int two)
        {
            return new OperationResult
            {
                Answer = one + two
            };
        }

        [HttpPut("/math")]
        public OperationResult Put([FromBody]OperationRequest request)
        {
            switch (request.Type)
            {
                case OperationType.Add:
                    return new OperationResult{Answer = request.One + request.Two, Method = "PUT"};
                
                case OperationType.Multiply:
                    return new OperationResult{Answer = request.One * request.Two, Method = "PUT"};
                
                case OperationType.Subtract:
                    return new OperationResult{Answer = request.One - request.Two, Method = "PUT"};
                
                default:
                    throw new ArgumentOutOfRangeException(nameof(request.Type));
            }
        }
        
        [HttpPost("/math")]
        public OperationResult Post([FromBody]OperationRequest request)
        {
            switch (request.Type)
            {
                case OperationType.Add:
                    return new OperationResult{Answer = request.One + request.Two, Method = "POST"};
                    
                case OperationType.Multiply:
                    return new OperationResult{Answer = request.One * request.Two, Method = "POST"};
                
                case OperationType.Subtract:
                    return new OperationResult{Answer = request.One - request.Two, Method = "POST"};
                
                default:
                    throw new ArgumentOutOfRangeException(nameof(request.Type));
            }
        }

Inside of an xUnit.Net project for your web project, you can write a test for that controller that exercises the full HTTP stack of your application like so:

        [Fact]
        public async Task get_happy_path()
        {
            // SystemUnderTest is from Alba
            // The "Startup" type would be the Startup class from your
            // web application. 
            using (var system = SystemUnderTest.ForStartup<WebApp.Startup>())
            {
                // Issue a request, and check the results
                var result = await system
                    .GetAsJson<OperationResult>("/math/add/3/4");
                
                result.Answer.ShouldBe(7);
            }
        }

        [Fact]
        public async Task post_and_expect_response()
        {
            using (var system = SystemUnderTest.ForStartup<WebApp.Startup>())
            {
                var request = new OperationRequest
                {
                    Type = OperationType.Multiply,
                    One = 3,
                    Two = 4
                };

                var result = await system
                    .PostJson(request, "/math")
                    .Receive<OperationResult>();
                
                result.Answer.ShouldBe(12);
                result.Method.ShouldBe("POST");
            }
        }

        [Fact]
        public async Task put_and_expect_response()
        {
            using (var system = SystemUnderTest.ForStartup<WebApp.Startup>())
            {
                var request = new OperationRequest
                {
                    Type = OperationType.Subtract,
                    One = 3,
                    Two = 4
                };

                var result = await system
                    .PutJson(request, "/math")
                    .Receive<OperationResult>();
                
                result.Answer.ShouldBe(-1);
                result.Method.ShouldBe("PUT");
            }
        }
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s