Following my previous post of Adding an API using ASP.NET Core, my focus in this post will be on testing the Application code following clean architecture.
The source code of this post is on GitHub. Because one single post is too long, I have created the following posts:
- Architecting ASP.NET Core applications
- Setting up the application core
- Introducing CQRS in the architecture
- Adding Validation using Fluent
- Creating the infrastructure project
- Adding an API using ASP.NET Core
- How testing the application code
- How adding an UI built in Blazor
- Improving on the application’s behaviour
Now, we have a large part of the application architecture ready. We can already work with API, and so that part, the back end, is in good shape. We did a lot of work to create a testable architecture for the application, but so far we haven’t really tested if it’s testable at all. Let us change that. In this post I’m going to be showing you how we can effectively test the different parts of the application we have created in the previous modules. Before we are going to be creating tests, I want to give you a brief overview of the different types of tests we can actually write. Then we’ll get to writing tests, and I’ll show you how we can write unit tests for the code we have created so far, as well as integration tests, testing time.
Understanding the Different Test Types
The main question we need to solve in this module it is quite exciting, if you ask me, did we do a good job? Is the architecture that we have created for MyTicket’s new system actually testable? How can we testing the application code? And will this help them in creating a soft resolution that can be changed easily over time?
We have kept loose coupling as a goal at all times, and that inherently will help us in achieving this. Now, let us see how far we get. Before we look at things in practice, let us start with understanding the different types of tests we can and probably should be writing as part of our application architecture. There are three main types of tests that we can distinguish. Unit test is the first one. It is probably the one you are most familiar with. We’ll start with this one in just a minute. Secondly, we can write integration tests. And finally, we also have functional tests. So, let’s start with unit tests.
Unit test
A lot has been written about it, but what is a unit test really? Well, I have a small definition here that more or less captures the essence of a unit test. A unit test is code that will typically, in an automated way, invoke code to be tested. It will check an assumption about the behaviour of that code under test. So, a unit test is really code that tests other code. I’m going to make an assumption about that piece of code’s behaviour, and that will decide whether or not the test has succeeded.
When writing unit tests, there’s a couple of things that we need to keep in mind.
First, we typically should test the public API of a class. Although there are definitely cases where you need to test the private API as well, in general, your unit test is a consumer of the code, and hence, you’d test the public API. Unit tests also need to run in isolation. That is something we have been trying to achieve with our architecture for some time already. We have applied dependency inversion, and that should make it possible that the code under test is free of dependencies we don’t want to include in our test.
When running tests, they should also be consistent in what they return. When I run a batch of tests multiple times, the results should always be the same. In order not to be a nuisance for developers, running tests should also be fast. If tests run too long, developers won’t be spending any time running the tests. Now very often too, these tests will get automated and will perhaps run as part of a continuous integration build. So why are we creating unit tests then? Why testing the application code?
Find bugs
Of course, to find bugs. We may not find bugs as a direct result of a unit test; however, when we make a change to a part of the code, the unit test can be seen as a guarantee that you haven’t introduced a bug while making the change. The result of that test should still be the same. Having unit tests for our code will allow us to make changes without fear.
When unit tests still runs without problems, we can be pretty sure that all is still well with our code. By creating tests, the overall quality of our code should also be improving. A unit test can be seen as the consumer for the actual code and makes us think more upfront about the design of our code.
Documentation code
Finally, we can use a good battery of unit tests as documentation for the code. I said unit tests are the consumer of the code. They describe how we can interact with the public API of our codebase. Very soon we’ll be writing some unit tests for the different parts of the application.
Integration tests
As a second important type of test, we have integration tests. As the name is giving away, an integration test will be more end to end. We have written are code so that we have most of it independent of the infrastructure, but that code too, that infrastructure code, needs to be tested. And not only does the code need to be tested, we need to make sure that the interaction between the different layers works well.
When the dependency is resolved, does everything still work as expected? That’s the type of information we want to get from an integration test. Now, typically, also more work to create and set up since we now need to work on setting up testing around infrastructure. This means that we’ll, for example, need to work with the database.
Testing database
Sometimes we’ll use an in‑memory database for an integration test. Entity Framework Core comes with an in‑memory option and we can use it for this. Alternatively, when we do testing on an actual database, then only to include code that restores the database in its original form after running the test, for example, by removing the newly added records again.
Functional tests
Finally, we have functional tests. Basically, this type of test is used to basically checked from the end user’s perspective if the system behaves as expected. People, who are involved in the creation of the system, but from a functional perspective, that is, can definitely help. But we can’t expect someone else to do the testing entirely manually, although that too is required. Functional testing therefore often comes down to bringing in a UI testing framework. We won’t be covering that in this course though, as it would take us too far.
Creating Unit Tests
What do you expect from a post title “Testing the Application code”? So, we are going to get busy writing the actual tests, and we’ll start with unit tests. To bring in tests in our project, we need to set up a few things. First, we are going to include a test project where we will have our unit tests in. We have different options here, but I’ll be using xUnit for this.
Then, since we want to test, for example, the code in the core project in isolation, we’ll need to bring in mock versions of the dependencies. The core code has dependencies that will be satisfied at runtime through dependency injection, so we’ll need to mock this out.
Some packages
Then we’ll be able to write this code in isolation. We can use for the mocking part manually crafted mock versions or a mocking framework. And yes, the testing will be made possible thanks to use of dependencies that will be injected. You’ll see that I’m again using a number of common packages when creating the unit tests. First, I’ll use Moq as the mocking framework. It makes it very straightforward to create mock versions of the dependencies we’ll need in our code under test.
I’ll also be using Shouldly, an easy‑to‑use assertion framework. Using Shouldly, we can more easily write readable assertions in our code, and I’ll show you how to do this in the demo.
Finally, as already said, we’ll use xUnit as the testing framework. We’ll use the template for this, Visual Studio comes with one, but that too is basically a package that is being added.
Creating Unit Tests for the Application Code
First, we’ll include unit tests for the Core code, and we’ll need to bring in some mocks as well. So let us now take a look at writing unit tests. And by means of example, I’m going to show you a unit test for the GetCategoriesListQueryHander
.
That is business logic, part of our application project. So I’m going to be testing this, and what I’m actually going to be testing is the code in the Handle method, because that’s what’s going to be triggered when I send a GetCategoriesList
query. Now, if we look at this code, we see that it has two dependencies, an IAsyncRepository
and Category and IMapper
, so those I will need to provide an implementation for in my unit test.
public class GetCategoriesListQueryHandler
: IRequestHandler<GetCategoriesListQuery, List<CategoryListVm>>
{
private readonly IAsyncRepository<Category> _categoryRepository;
private readonly IMapper _mapper;
public GetCategoriesListQueryHandler(IMapper mapper,
IAsyncRepository<Category> categoryRepository)
{
_mapper = mapper;
_categoryRepository = categoryRepository;
}
public async Task<List<CategoryListVm>> Handle(GetCategoriesListQuery request,
CancellationToken cancellationToken)
{
var allCategories = (await _categoryRepository.ListAllAsync()).OrderBy(x => x.Name);
return _mapper.Map<List<CategoryListVm>>(allCategories);
}
}
Test project overview
Under the Test folder, I’ve created this unit test project, and as you can see, it’s an xUnit unit testing project. It also has a dependency on Moq and Shouldly, and we’re going to be testing code in the Application project, so I also have a reference to the Application project. That’s the only thing I should have in terms of references.
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.15.2" />
<PackageReference Include="Shouldly" Version="4.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyTicket.TicketManagement.Application\MyTicket.TicketManagement.Application.csproj" />
<ProjectReference Include="..\MyTicket.TicketManagement.Domain\MyTicket.TicketManagement.Domain.csproj" />
</ItemGroup>
Write the unit test
Now, let us take a look at the unit test itself. You can see the unit test. It’s attributed with the Fact attribute, meaning that this is a unit test. I’m going to be instantiating my handler. And in order to be able to do that, I need to provide it with implementations for the two dependencies.
public class GetCategoriesListQueryHandlerTests
{
private readonly IMapper _mapper;
private readonly Mock<IAsyncRepository<Category>> _mockCategoryRepository;
public GetCategoriesListQueryHandlerTests()
{
_mockCategoryRepository = RepositoryMocks.GetCategoryRepository();
var configurationProvider = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MappingProfile>();
});
_mapper = configurationProvider.CreateMapper();
}
[Fact]
public async Task GetCategoriesListTest()
{
var handler = new GetCategoriesListQueryHandler(_mapper, _mockCategoryRepository.Object);
var result = await handler.Handle(new GetCategoriesListQuery(), CancellationToken.None);
result.ShouldBeOfType<List<CategoryListVm>>();
result.Count.ShouldBe(4);
}
}
In the constructor, I’m creating those. I’m using the Moq framework to provide an implementation for the IAsyncRepository
. I’ve written a static GetCategoryRepository
on a RepositoryMocks
class. In the RepositoryMocks
, I’ve created in the GetCategoryRepository
a number of hard‑coded categories.
public class RepositoryMocks
{
public static Mock<IAsyncRepository<Category>> GetCategoryRepository()
{
var concertGuid = Guid.Parse("{B0788D2F-8003-43C1-92A4-EDC76A7C5DDE}");
var musicalGuid = Guid.Parse("{6313179F-7837-473A-A4D5-A5571B43E6A6}");
var playGuid = Guid.Parse("{BF3F3002-7E53-441E-8B76-F6280BE284AA}");
var conferenceGuid = Guid.Parse("{FE98F549-E790-4E9F-AA16-18C2292A2EE9}");
var categories = new List<Category>
{
new Category
{
CategoryId = concertGuid,
Name = "Concerts"
},
new Category
{
CategoryId = musicalGuid,
Name = "Musicals"
},
new Category
{
CategoryId = conferenceGuid,
Name = "Conferences"
},
new Category
{
CategoryId = playGuid,
Name = "Plays"
}
};
var mockCategoryRepository = new Mock<IAsyncRepository<Category>>();
mockCategoryRepository.Setup(repo => repo.ListAllAsync()).ReturnsAsync(categories);
mockCategoryRepository.Setup(repo => repo.AddAsync(It.IsAny<Category>())).ReturnsAsync(
(Category category) =>
{
categories.Add(category);
return category;
});
return mockCategoryRepository;
}
}
Then, using Mock
, I’m creating a mock implementation of the IAsyncRepository
stating that when I call the ListAllAsync
, it should just return me all categories. And when I’m calling AddAsync
, it should be adding that category to the list of categories. That mock version is then returned.
I’m also creating an instance of AutoMapper, passing it our MappingProfile
class so it knows how to do the mappings. So, now we’re ready with the setup, and we can create the GetCategoriesListQuery
handler, passing it our mapper and our repository.
Then, I can called in Handle
method, which expects a GetCategoriesListQuery
. That will then return a list of CategoryListVms
. Indeed, that’s what we were mapping to here. Now we’re going to assert the result, and I’m going to see if it’s effectively a list of CategoryListVms
that I’m getting back.
Because in my mock I created four categories, I’m now also expecting that the number of items in the result should be four. And for these assertions, I’m using the Shouldly framework.
Write CreateCategory test
So let’s run this test, and that seems to have worked fine. Now there’s also a test for the creation of a category, which is pretty similar. I’m then going to use the CreateCategory
handler. It expects the same dependencies. I’m then going to create a category, which, behind the scenes, is going to create that category. If no validation errors occur, that is, I’m then going to ask my mock repository to list again the categories. And now, because I’ve added one, the result should be five. I can also run this unit test, and that seems to be fine as well. So now you see how our architecture lends itself well to writing unit tests.
Writing Integration Tests for Infrastructure
We now have some unit tests working, and yes, our application should contain probably quite a few more, but now you understand how they should be created. Let’s now turn our attention to creating integration tests.
When writing these, we’re basically going to be writing a test that integrates several layers. In the first demo, I’m going to be writing an integration test that tests part of the infrastructure code. Under the Test folder, there’s also an IntegrationTests project, and in there I have an integration test for the DbContext.
public class MyTicketDbContextTests
{
private readonly MyTicketDbContext _myTicketDbContext;
private readonly Mock<ILoggedInUserService> _loggedInUserServiceMock;
private readonly string _loggedInUserId;
public MyTicketDbContextTests()
{
var dbContextOptions = new DbContextOptionsBuilder<MyTicketDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
_loggedInUserId = "00000000-0000-0000-0000-000000000000";
_loggedInUserServiceMock = new Mock<ILoggedInUserService>();
_loggedInUserServiceMock.Setup(m => m.UserId).Returns(_loggedInUserId);
_myTicketDbContext = new MyTicketDbContext(dbContextOptions, _loggedInUserServiceMock.Object);
}
[Fact]
public async void Save_SetCreatedByProperty()
{
var ev = new Event() {EventId = Guid.NewGuid(), Name = "Test event" };
_myTicketDbContext.Events.Add(ev);
await _myTicketDbContext.SaveChangesAsync();
ev.CreatedBy.ShouldBe(_loggedInUserId);
}
}
Testing database
Now, when we want to test something on the DbContext, we actually need to write an integration test. Entity Framework Core comes with the in‑memory database, which makes integration tests using a DbContext very easy. Now let’s take a look at the MyTicketDbContext.
As you can see, I have two constructors in the code above, I have the regular constructor, and I also have one that expects an ILoggedInUserService
. Now, I haven’t explained this but I invite you to take a look at this code at your own pace. All this is available with the downloads of the code. The ILoggedInUserService
will, in its implementation, returned the name of the logged‑in user. Now that I’m also using here in this SaveChangesAsync
method, which is the one I want to write an integration test for now. I remained you SaveChangesAsync
is in the MyTicketDbContext
.
So, the SaveChangesAsync
method is going to automatically, when an entity is being saved, set the CreatedDate and CreatedBy, or LastModifiedDate and LastModifiedBy values that are defined on the AuditableEntity
. Then, what I want to test is that this actually works.
Now, I’m actually going to test that the CreatedBy
value is actually set. Now in order test this, I’m going to again provide an implementation, a mock implementation for the ILoggedInUserService
, which is going to just return a hard‑coded GUID. That’s what you see here. Then in my test, I’m going to create a new event and then call just a plain Add method on that DbSet
. By calling the Add
, an entity is going to be added. I call the SaveChangesAsync
, and then using ShouldBe
, I’m going to check that the CreatedBy
value is actually set. So here we see how we can easily write an integration test that involves the DB Code text.
Writing Integration Tests for the API Controllers
Now, let’s take a look at how we can test the API code, so the code part of the controllers. That too will require an integration test. Let’s take a look at how we can write an integration test for our API controllers.
Now, when we want to test an API function, an API method that is, I actually need to be able to talk with a running application. Now this we can easily do using the WebApplicationFactory
, which comes with ASP.NET Core. Here’s my IntegrationTests project, which contains pretty much the same references that we had up until now but also includes now the Mvc.Testing PackageReference.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.15.2" />
<PackageReference Include="Shouldly" Version="4.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.10" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyTicket.TicketManagement.Api\MyTicket.TicketManagement.Api.csproj" />
<ProjectReference Include="..\MyTicket.TicketManagement.Application\MyTicket.TicketManagement.Application.csproj" />
<ProjectReference Include="..\MyTicket.TicketManagement.Domain\MyTicket.TicketManagement.Domain.csproj" />
<ProjectReference Include="..\MyTicket.TicketManagement.Persistence\MyTicket.TicketManagement.Persistence.csproj" />
</ItemGroup>
</Project>
And you’ll also see that I’m, of course, referencing the API project since I need for my test to be able to talk with the API. What I want to test is the category/all
API call. Now, how am I going to be able to run that test?
Run tests against APIs
Well, I’ve created a CustomWebApplicationFactory, which inherits from the WebApplicationFactory, in which we then set up the WebHost
. We pass it here at Startup.cs
, so that’s the startup of the API project, which will, therefore, configure our application in‑memory. I’m also going to specify that for the MyTicketDbContext
, I’m going to be using an in‑memory version of that DbContext
.
CustomWebApplicationFactory code
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.AddDbContext<MyTicketDbContext>(options =>
{
options.UseInMemoryDatabase("MyTicketDbContextInMemoryTest");
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var context = scopedServices.GetRequiredService<MyTicketDbContext>();
var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
context.Database.EnsureCreated();
try
{
Utilities.InitializeDbForTests(context);
}
catch (Exception ex)
{
logger.LogError(ex,
$"An error occurred seeding the database with test messages. Error: {ex.Message}");
}
};
});
}
public HttpClient GetAnonymousClient()
{
return CreateClient();
}
}
Explanation
Now further on, I also load that database with, again, some hardcoded data in this case for categories. And I save those categories to that DbContext
, so to that in‑memory database. So this configures our web application that now uses as its database an in‑memory database. So this is the full web application. So this will, behind the scenes, have set it up using my application project, using our infrastructure project, but the main difference is that it’s running against an in‑memory database, so I actually can test using that. That’s what I’m actually going to be doing here.
public class CategoryControllerTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly CustomWebApplicationFactory<Startup> _factory;
public CategoryControllerTests(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task ReturnsSuccessResult()
{
var client = _factory.GetAnonymousClient();
var response = await client.GetAsync("/api/category/all");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<List<CategoryListVm>>(responseString);
Assert.IsType<List<CategoryListVm>>(result);
Assert.NotEmpty(result);
}
}
In my actual test, I’m going to ask for a client against that API. I’m then going to invoke the category/all
API call. And I’m going to check that it’s effectively returning me a 200
success status code. Once that is true, then I’m also going to read out the result, and I’m going to pause that. The result is effectively JSON, and I’m going to deserialize that in a list of CategoryListVms
, which is what our action method was going to return. And then I have a couple of assertions checking that this is effectively the type I’m getting back and that also the result isn’t empty.
7 thoughts on “How testing the Application code”