Quantcast
Channel: asp.net 5 rc1 – .NET Liberty
Viewing all articles
Browse latest Browse all 13

ASP.NET 5 Web API Unit Testing

$
0
0

I’m currently building a new ASP.NET 5 Web API using pure .NET Core (not the full .NET framework). Before I can call my job done, I need to test my code and prove to myself that it works as I expect it to.

Unit Testing vs. Integration Testing

One good approach is to write high level integration tests – send HTTP requests to my Web API and validate that I get the responses I expect. This works well for catching functional problems with my Web API. I’ll be sharing how I do that in the next post.

It is valuable to know if my API isn’t behaving as expected – and integration tests do a great job of that. The downside to integration tests is: when something breaks, it can sometimes be hard to track down the root cause.

When something breaks, I typically have to dig deeper into the code in order to find out what caused the issue. This is where I find unit tests to shine. I can test individual units of logic of my Web API Controller with a variety of inputs and be reasonably confident that my controller is interacting with its dependencies correctly and returns the correct response.

armen-shimoon-webapi-unit-t

.NET Core Limitations

In previous projects, I’ve made heavy use of frameworks like Moq in order to inject fake versions (mocks) of dependencies into my class under test at runtime. This allows me to isolate my code and focus on how it interacts with its dependencies.

A mock allows me to create a fake implementation of any interface at runtime and use that in place of the real thing. I can arrange the mock to return some canned data as well as assert that various methods on the mock were indeed called with the correct parameters.

Savior: LightMock.vNext

The options for mocking frameworks on .NET Core are currently limited – my go-to Moq is not yet supported. However I was able to make use of the LightMock .NET Core port called LightMock.vNext to achieve much of the same.

The only pain point right now is that with Moq, I can create a mock object from an interface at runtime without actually implementing the interface on some fake test object.

With LightMock.vNext, I have to create a test-only object that implements the interface I want to mock, and pass through calls to the LightMock.vNext framework via an IInvocationContext<T> that is passed into the constructor.

It’s a little bit more heavy lifting than I’d prefer, but it gets the job done, and I don’t have to run my unit tests on the full .NET framework or Mono. Success.

Let me show you some of the unit tests I was able to write for a basic Web API Controller called HabitsController below.

Test Fakes for HabitsController Dependencies

The Constructor

Lets start off by looking at the constructor on my Web API Controller to see what dependencies it needs.

private readonly IUserContextService _userContextService;
private readonly IMapper<HabitDomainModel, HabitDto> _dtoMapper;
private readonly IMapper<HabitDto, HabitDomainModel> _domainMapper;
private readonly ILogger<HabitsController> _logger;
private readonly IHabitRepository _repository;

public HabitsController(
    ILogger<HabitsController> logger,
    IHabitRepository repository,
    IUserContextService userContextService,
    IMapper<HabitDomainModel, HabitDto> dtoMapper,
    IMapper<HabitDto, HabitDomainModel> domainMapper)
{
    _logger = logger;
    _repository = repository;
    _userContextService = userContextService;
    _dtoMapper = dtoMapper;
    _domainMapper = domainMapper;
}

As you can see, I’m making pretty heavy use of the ASP.NET 5 dependency injection mechanism. The benefit here is I can inject fake or mock dependencies for my unit tests and focus on testing the logic of my controller on its own.

Lets look at some of the dependencies I’ll have to fake/stub/mock for my unit tests.

ILogger<T>

The first challenge was injecting a logger. I decided to create a stub logger that I could use wherever I needed a logger. As you can see, it does nothing. That’s fine because I’m not interested in testing my logging at this point.

using System;
using Microsoft.Extensions.Logging;

namespace MyLifeStack.DataService.Test.Stubs
{
    public class StubLogger<T> : ILogger<T>
    {
        public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
        {
            // no op
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return false;
        }

        public IDisposable BeginScopeImpl(object state)
        {
            return null;
        }
    }
}

IHabitRepository

Next, I need an IHabitRepository. I could take an approach like above where I create a stub or fake repository implementation.

Since my Controller will be interacting fairly heavily with the repository, I’d like to be able to programmatically arrange fake responses and assert various methods were called on the repository by my controller.

For those reasons, I decided to use LightMock.vNext to create a mock:

using System;
using System.Collections.Generic;
using LightMock;
using MyLifeStack.Core.Models;
using MyLifeStack.Data.Repositories;

namespace MyLifeStack.DataService.Test.Mocks
{
    public class MockHabitRepository : IHabitRepository
    {
        private readonly IInvocationContext<IHabitRepository> _context;

        public MockHabitRepository(IInvocationContext<IHabitRepository> context)
        {
            _context = context;
        }

        public IList<Habit> GetHabits(Guid userId)
        {
            return _context.Invoke(m => m.GetHabits(userId));
        }

        public Habit Get(Guid userId, Guid habitId)
        {
            return _context.Invoke(m => m.Get(userId, habitId));
        }

        public Habit Create(Habit habit)
        {
            return _context.Invoke(m => m.Create(habit));
        }

        public Habit Update(Habit habit)
        {
            return _context.Invoke(m => m.Update(habit));
        }

        public void Delete(Guid userId, Guid habitId)
        {
            _context.Invoke(m => m.Delete(userId, habitId));
        }
    }
}

Nothing too fancy – I just have to implement the repository interface IHabitRepository, then pass off all calls to the IInvocationContext<IHabitRepository> that is provided by LightMock.vNext.

Notice how I’m using lambda expressions to invoke methods on the IHabitRepository interface? LightMock.vNext is making use of some “expression magic” to record the calls made to my mock object without having to actually emit a new type at runtime! Pretty cool.

IUserContextService

Same story as the repository, I decided to create a mock here. I probably could have used a stub to just return a canned value since the interface is so simple, but I was having too much fun with LightMock.vNext. So I wound up with another mock.

using System;
using LightMock;
using MyLifeStack.DataService.Services;

namespace MyLifeStack.DataService.Test.Mocks
{
    public class MockUserContextService : IUserContextService
    {
        private readonly IInvocationContext<IUserContextService> _context;

        public MockUserContextService(IInvocationContext<IUserContextService> context)
        {
            _context = context;
        }

        public Guid UserId
        {
            get { return _context.Invoke(m => m.UserId); }
        }
    }
}

IMapper<HabitDomainModel, HabitDto> and IMapper<HabitDto, HabitDomainModel>

As you will see, I prefer to define data transfer objects for sending and receiving data from my Web API Controller, and work with only domain objects inside my application. That’s what these mappers are for – they convert from one type to the other.

In this case, I decided to use the real implementations of these interfaces – all they do is some left-hand right-hand assignment code. Some unit testing purists might suggest using a mock here as well, and I might agree with them: I’m not actually interested in exercising the mapping code in my controller unit tests. It would arguably be cleaner to mock that part out and just ensure my controller interacts with the mock mappers instead.

Perhaps when my mapping logic gets more complicated I’ll do just that. But for now using the real implementations is good enough. It will be easy enough to replace them with mocks if I need them in the future anyway.

The Unit Tests

I created a new project to house my unit tests and added dependencies on my Web API project. In addition, I need to reference xunit and xunit.runner.dnx for running the unit tests and LightMock.vNext for mocking.

MyLifeStack.DataService.Test – project.json

{
    "version": "1.0.0-*",
    "description": "Tests for MyLifeStack.DataService",
    "authors": [ "Armen Shimoon" ],
    "tags": [ "" ],
    "projectUrl": "",
    "licenseUrl": "",

    "frameworks": {
        "dnxcore50": {
            "dependencies": {
                "Microsoft.CSharp": "4.0.1-beta-23516",
                "System.Collections": "4.0.11-beta-23516",
                "System.Linq": "4.0.1-beta-23516",
                "System.Runtime": "4.0.21-beta-23516",
                "System.Threading": "4.0.11-beta-23516",
                "xunit": "2.1.0-*",
                "xunit.runner.dnx": "2.1.0-*",
                "MyLifeStack.DataService": "1.0.0",
                "MyLifeStack.DataService.Contract": "1.0.0",
                "LightMock.vNext":  "1.0.1"
            }
        }
    },
    "commands": {
        "test": "xunit.runner.dnx"
    }
}

HabitsControllerTests.cs

This is where my unit tests will live. Lets look at the constructor – its responsible for creating my test dependencies and injecting them into my subject under test – HabitsController. This code should be pretty self explanatory.

namespace MyLifeStack.DataService.Test
{
    public class HabitsControllerTests
    {
        private readonly MockContext<IHabitRepository> _repositoryContext;
        private readonly HabitsController _subject;
        private readonly Guid _userId;

        public HabitsControllerTests()
        {
            _userId = Guid.NewGuid();
            var userContext = new MockContext<IUserContextService>();
            userContext.Arrange(m => m.UserId).Returns(_userId);
            var userMock = new MockUserContextService(userContext);

            _repositoryContext = new MockContext<IHabitRepository>();
            var repositoryMock = new MockHabitRepository(_repositoryContext);

            var dtoMapper = new HabitDomainToDtoMapper();
            var domainMapper = new HabitDtoToDomainMapper();

            var logger = new StubLogger<HabitsController>();

            _subject = new HabitsController(
                logger,
                repositoryMock,
                userMock,
                dtoMapper,
                domainMapper);
        }

        // ...
    }
}

GET /api/habits

In this same class, lets look at my first test for fetching all Habits.

This one is pretty simple: I create a couple of fake domain models that my repository should return. I then invoke my controller’s Get method and validate that it fetched and converted the domain models to DTOs correctly.

Code Under Test

[HttpGet]
public IEnumerable<HabitDto> Get()
{
    return ToDtos(_repository.GetHabits(_userContextService.UserId));
}

Test

[Fact]
public void get_all()
{
    // Arrange
    var domain1 = new HabitDomainModel
    {
        HabitId = Guid.NewGuid(),
        UserId = _userId,
        Name = "Habit 1"
    };
    var domain2 = new HabitDomainModel
    {
        HabitId = Guid.NewGuid(),
        UserId = _userId,
        Name = "Habit 2"
    };
    var domains = new List<HabitDomainModel>
    {
        domain1,
        domain2
    };
    _repositoryContext.Arrange(m => m.GetHabits(_userId)).Returns(domains);

    // Act
    var dtos = _subject.Get().ToList();

    // Assert
    Assert.Equal(2, dtos.Count);

    var dto1 = dtos[0];
    Assert.Equal(domain1.HabitId, dto1.HabitId);
    Assert.Equal(domain1.Name, dto1.Name);

    var dto2 = dtos[1];
    Assert.Equal(domain2.HabitId, dto2.HabitId);
    Assert.Equal(domain2.Name, dto2.Name);

    _repositoryContext.Assert(m => m.GetHabits(_userId));
}

GET /api/habits/{id}

Now I want to test if I can fetch a single Habit. Similar to above, I just had to arrange my repository mock to return a fake domain model, then validate it was converted to a DTO and returned correctly.

Code Under Test

[HttpGet("{habitId}")]
public HabitDto Get(Guid habitId)
{
    _logger.LogInformation("Requesting Habit with id {habitId}", habitId);
    return ToDto(_repository.Get(_userContextService.UserId, habitId));
}

Test

[Fact]
public void get_single()
{
    // Arrange
    var habitId = Guid.NewGuid();
    var domain = new HabitDomainModel
    {
        HabitId = habitId,
        UserId = _userId,
        Name = "Test"
    };
    _repositoryContext.Arrange(m => m.Get(_userId, habitId)).Returns(domain);

    // Act
    var dto = _subject.Get(habitId);

    // Assert
    Assert.Equal(habitId, dto.HabitId);
    Assert.Equal("Test", dto.Name);
    _repositoryContext.Assert(m => m.Get(_userId, habitId));
}

POST /api/habits/

Next I want to validate that I can create a new Habit via the Post method. Here I want to validate that a new HabitDomainModel is actually given to the repository with the correct fields set, and that the response returned from the method is what I expect.

Code Under Test

[HttpPost]
public HabitDto Post([FromBody]HabitDto value)
{
    _logger.LogInformation($"Beginning POST: {value}");
    if (!ModelState.IsValid)
    {
        Response.StatusCode = (int) HttpStatusCode.BadRequest;
        return null;
    }
    _logger.LogInformation("Posting new Habit with name {habitName}", value?.Name);
    var domain = ToDomainModel(value);
    domain.UserId = _userContextService.UserId;
    var dto = ToDto(_repository.Create(domain));
    _logger.LogInformation($"Created new Habit with ID: {dto.HabitId}");
    return dto;

Test

[Fact]
public void post()
{
    // Arrange
    var habitId = Guid.NewGuid();
    var dto = new HabitDto
    {
        HabitId = habitId,
        Name = "Test"
    };
    var newHabitId = Guid.NewGuid();
    var domain = new HabitDomainModel
    {
        HabitId = newHabitId,
        Name = "Test",
        UserId = _userId
    };
    _repositoryContext.Arrange(m => m.Create(The<HabitDomainModel>.IsAnyValue)).Returns(domain);

    // Act
    var created = _subject.Post(dto);

    // Assert
    Assert.Equal("Test", created.Name);
    Assert.NotEqual(habitId, created.HabitId); // API should assign a new ID
    Assert.Equal(newHabitId, created.HabitId);
    _repositoryContext.Assert(
        m => m.Create(
            The<HabitDomainModel>.Is(
                d => d.Name == "Test" && d.UserId == _userId)));
}

PUT /api/habits/{id}

Similar to above, I want to test that I can update an existing Habit. In this test I don’t actually update anything – I just validate that the repository’s Update method was called as I expect. I can test the repository implementation separately to ensure it is actually fetching and updating an existing record.

Code Under Test

[HttpPut("{habitId}")]
public HabitDto Put(Guid habitId, [FromBody]HabitDto value)
{
    if (!ModelState.IsValid)
    {
        Response.StatusCode = (int) HttpStatusCode.BadRequest;
        return null;
    }
    if (habitId != value.HabitId)
    {
        Response.StatusCode = (int) HttpStatusCode.BadRequest;
        return null;
    }
    _logger.LogInformation("Updating Habit with name {habitId}", habitId);
    var domain = ToDomainModel(value);
    domain.UserId = _userContextService.UserId;
    return ToDto(_repository.Update(domain));
}

Test 1 – Happy Path

[Fact]
public void put()
{
    // Arrange
    var habitId = Guid.NewGuid();
    var dto = new HabitDto
    {
        HabitId = habitId,
        Name = "Test"
    };
    var domain = new HabitDomainModel
    {
        HabitId = habitId,
        Name = "Test"
    };
    _repositoryContext.Arrange(m => m.Update(The<HabitDomainModel>.IsAnyValue)).Returns(domain);

    // Act
    var updated = _subject.Put(habitId, dto);

    // Assert
    Assert.Equal("Test", updated.Name);
    Assert.Equal(habitId, updated.HabitId);
    _repositoryContext.Assert(
        m => m.Update(
            The<HabitDomainModel>.Is(
                d => d.HabitId == habitId && d.UserId == _userId && d.Name == "Test")));
}

Test 2 – Invalid ID

[Fact]
public void put_wrong_id()
{
    // Arrange
    var habitId = Guid.NewGuid();
    var dto = new HabitDto
    {
        HabitId = habitId,
        Name = "Test"
    };
    var domain = new HabitDomainModel
    {
        HabitId = habitId,
        Name = "Test"
    };
    _repositoryContext.Arrange(m => m.Update(The<HabitDomainModel>.IsAnyValue)).Returns(domain);
    _subject.ActionContext = new ActionContext
    {
        HttpContext = new DefaultHttpContext() // Needed since the controller sets Response.StatusCode
    };

    // Act
    var updated = _subject.Put(Guid.NewGuid(), dto);

    // Assert
    Assert.Null(updated);
    Assert.Equal((int) HttpStatusCode.BadRequest, _subject.Response.StatusCode);
}

DELETE /api/habits/{id}

Finally, I can test that my controller can delete a Habit. This is a case where using a mock to validate the behavior between my class under test and its dependencies is extremely helpful: since there’s no output from this method it would be hard to test it otherwise.

Code Under Test

[HttpDelete("{habitId}")]
public void Delete(Guid habitId)
{
    _logger.LogInformation("Deleting Habit with id {habitId}", habitId);
    _repository.Delete(_userContextService.UserId, habitId);
}

Test

[Fact]
public void delete()
{
    // Arrange
    var habitId = Guid.NewGuid();

    // Act
    _subject.Delete(habitId);

    // Assert
    _repositoryContext.Assert(m => m.Delete(_userId, habitId));
}

Done

Hopefully this was helpful in showing some of the basics of unit testing a Web API controller using xUnit and LightMock.vNext. Most of the tests here were “happy path” tests – ensuring that my system behaves correctly when given correct data.

Ideally I would fill out more tests cases that exercise some error cases – providing null inputs, model state violations, and so on. When I get to that, I’ll share it here.

Finally, while I’ve added some valuable tests here, it still doesn’t test how my application will behave while it’s running and interacting with real dependencies. In the next post I’m going to show you how I added some integration tests for this exact Web API that give me even more test coverage and confidence that my code is behaving correctly. Check back soon if you’re interested in that kinda stuff.


Viewing all articles
Browse latest Browse all 13

Latest Images

Trending Articles





Latest Images