In this document
- Get the source code from the GitHub repository.
Introduction
In this article, I'll show how to create unit tests for ASP.NET Boilerplate based projects. Instead of creating a new application to be tested, I'll use the same application developed in this article (Using AngularJS, ASP.NET MVC, Web API and EntityFramework to build NLayered Single Page Web Applications). Solution structure is like that:
We will test Application Services of the project. It includes SimpleTaskSystem.Core, SimpleTaskSystem.Application and SimpleTaskSystem.EntityFramework projects. You can read this article to see how to build this application. Here, I'll focus on testing.
Create a test project
I created a new Class Library project named SimpleTaskSystem.Test and added following nuget packages:
- Abp.TestBase: Provides some base classes to make testing easier for ABP based projects.
- Abp.EntityFramework: We use EntityFramework 6.x as ORM.
- Effort.EF6: Makes it possible to create a fake, in-memory database for EF that is easy to use.
- xunit: The testing framework we'll use. Also, added xunit.runner.visualstudio package to run tests in Visual Studio. This package was pre-release when I writing this article. So, I selected 'Include Prerelease' in nuget package manager dialog.
- Shouldly: This library makes easy to write assertions.
When we add these packages, their dependencies will also be added automatically. Lastly, we should add reference to SimpleTaskSystem.Application, SimpleTaskSystem.Core and SimpleTaskSystem.EntityFramework assemblies since we will test these projects.
Preparing a base test class
To create test classes easier, I'll create a base class that prepares a fake database connection:
/// <summary> /// This is base class for all our test classes. /// It prepares ABP system, modules and a fake, in-memory database. /// Seeds database with initial data (<see cref="SimpleTaskSystemInitialDataBuilder"/>). /// Provides methods to easily work with DbContext. /// </summary> public abstract class SimpleTaskSystemTestBase : AbpIntegratedTestBase<SimpleTaskSystemTestModule> { protected SimpleTaskSystemTestBase() { //Seed initial data UsingDbContext(context => new SimpleTaskSystemInitialDataBuilder().Build(context)); } protected override void PreInitialize() { //Fake DbConnection using Effort! LocalIocManager.IocContainer.Register( Component.For<DbConnection>() .UsingFactoryMethod(Effort.DbConnectionFactory.CreateTransient) .LifestyleSingleton() ); base.PreInitialize(); } public void UsingDbContext(Action<SimpleTaskSystemDbContext> action) { using (var context = LocalIocManager.Resolve<SimpleTaskSystemDbContext>()) { context.DisableAllFilters(); action(context); context.SaveChanges(); } } public T UsingDbContext<T>(Func<SimpleTaskSystemDbContext, T> func) { T result; using (var context = LocalIocManager.Resolve<SimpleTaskSystemDbContext>()) { context.DisableAllFilters(); result = func(context); context.SaveChanges(); } return result; } }
This base class extends AbpIntegratedTestBase. It's a base class which initializes the ABP system. Defines LocalIocContainer property, that is a IIocManager object. Each test will work with it's dedicated IIocManager. Thus, tests will be isolated from each other.
We should create a module dedicated for tests. It's SimpleTaskSystemTestModule here:
[DependsOn(
typeof(SimpleTaskSystemDataModule),
typeof(SimpleTaskSystemApplicationModule),
typeof(AbpTestBaseModule)
)]
public class SimpleTaskSystemTestModule : AbpModule
{
}
This module only defines depended modules, which will be tested and the AbpTestBaseModule.
In the SimpleTaskSystemTestBase's PreInitialize method, we're registering DbConnection to dependency injection system using Effort (PreInitialize method is used to run some code just befor ABP initialized). We registered it as Singleton (for LocalIocConainer). Thus, same database (and connection) will be used in a test even we create more than one DbContext in same test. SimpleTaskSystemDbContext must have a constructor getting DbConnection in order to use this in-memory database. So, I added the constructor below that accepts a DbConnection:
public class SimpleTaskSystemDbContext : AbpDbContext { public virtual IDbSet<Task> Tasks { get; set; } public virtual IDbSet<Person> People { get; set; } public SimpleTaskSystemDbContext() : base("Default") { } public SimpleTaskSystemDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { } //This constructor is used in tests public SimpleTaskSystemDbContext(DbConnection connection) : base(connection, true) { } }
In the constructor of SimpleTaskSystemTestBase, we're also creating an initial data in the database. This is important, since some tests require a data present in the database. SimpleTaskSystemInitialDataBuilder class fills database as shown below:
public class SimpleTaskSystemInitialDataBuilder { public void Build(SimpleTaskSystemDbContext context) { //Add some people context.People.AddOrUpdate( p => p.Name, new Person {Name = "Isaac Asimov"}, new Person {Name = "Thomas More"}, new Person {Name = "George Orwell"}, new Person {Name = "Douglas Adams"} ); context.SaveChanges(); //Add some tasks context.Tasks.AddOrUpdate( t => t.Description, new Task { Description = "my initial task 1" }, new Task { Description = "my initial task 2", State = TaskState.Completed }, new Task { Description = "my initial task 3", AssignedPerson = context.People.Single(p => p.Name == "Douglas Adams") }, new Task { Description = "my initial task 4", AssignedPerson = context.People.Single(p => p.Name == "Isaac Asimov"), State = TaskState.Completed }); context.SaveChanges(); } }
SimpleTaskSystemTestBase's UsingDbContext methods makes it easier to create DbContextes when we need to directly use DbContect to work with database. In constructor, we used it. Also, we will see how to use it in tests.
All our test classes will be inherited from SimpleTaskSystemTestBase. Thus, all tests will be started by initializing ABP, using a fake database with an initial data. We can also add common helper methods to this base class in order to make tests easier.
Creating first test
We will create first unit test to test CreateTask method of TaskAppService class. TaskAppService class and CreateTask method are defined as shown below:
public class TaskAppService : ApplicationService, ITaskAppService { private readonly ITaskRepository _taskRepository; private readonly IRepository<Person> _personRepository; public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository) { _taskRepository = taskRepository; _personRepository = personRepository; } public void CreateTask(CreateTaskInput input) { Logger.Info("Creating a task for input: " + input); var task = new Task { Description = input.Description }; if (input.AssignedPersonId.HasValue) { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value); } _taskRepository.Insert(task); } //...other methods }
In unit test, generally, dependencies of testing class is mocked (by creating fake implementations using some mock frameworks like Moq and NSubstitute). This makes unit testing harder, especially when dependencies grows.
We will not do it like that since we're using dependency injection. All dependencies will be filled automatically by dependency injection with real implementations, not fakes. Only fake thing is the database. Actually, this is an integration test since it not only tests the TaskAppService, but also tests repositories. Even, we're testing it with validation, unit of work and other infrastructures of ASP.NET Boilerplate. This is very valuable since we're testing the application much more realistic.
So, let's create first test to test CreateTask method.
public class TaskAppService_Tests : SimpleTaskSystemTestBase { private readonly ITaskAppService _taskAppService; public TaskAppService_Tests() { //Creating the class which is tested (SUT - Software Under Test) _taskAppService = LocalIocManager.Resolve<ITaskAppService>(); } [Fact] public void Should_Create_New_Tasks() { //Prepare for test var initialTaskCount = UsingDbContext(context => context.Tasks.Count()); var thomasMore = GetPerson("Thomas More"); //Run SUT _taskAppService.CreateTask( new CreateTaskInput { Description = "my test task 1" }); _taskAppService.CreateTask( new CreateTaskInput { Description = "my test task 2", AssignedPersonId = thomasMore.Id }); //Check results UsingDbContext(context => { context.Tasks.Count().ShouldBe(initialTaskCount + 2); context.Tasks.FirstOrDefault(t => t.AssignedPersonId == null && t.Description == "my test task 1").ShouldNotBe(null); var task2 = context.Tasks.FirstOrDefault(t => t.Description == "my test task 2"); task2.ShouldNotBe(null); task2.AssignedPersonId.ShouldBe(thomasMore.Id); }); } private Person GetPerson(string name) { return UsingDbContext(context => context.People.Single(p => p.Name == name)); } }
We inherited from SimpleTaskSystemTestBase as described before. In a unit test, we should create the object this will be tested. In the constructor, I used LocalIocManager (dependency injection manager) to create an ITaskAppService (it creates TaskAppService since it implements ITaskAppService). In this way, I got rid of creating mock implementations of dependencies.
Should_Create_New_Tasks is the test method. It's decorated with the Fact attribute of xUnit. Thus, xUnit understand that this is a test method, and it runs the method.
In a test method, we generally follow AAA pattern which consists of three steps:
- Arrange: Prepare for the test
- Act: Run the SUT (software under test - the actual testing code)
- Assert: Check and verify the result.
In Should_Create_New_Tasks method, we will create two tasks, one will be assigned to Thomas More. So, our three steps are:
- Arrange: We get the person (Thomas More) from database to obtain his Id and the current task count in database (Also, we created the TaskAppService in the constructor).
- Act: We're creating two tasks using TaskAppService.CreateTask method.
- Assert: We're checking if task count increased by 2. We're also trying to get created tasks from database to see if they are correctly inserted to the database.
Here, UsingDbContext method helps us while working directly with DbContext. If this test success, we understand that CreateTask method can create Tasks if we supply valid inputs. Also, repository is working since it inserted Tasks to the database.
To run tests, we're opening Visual Studio Test Explorer by selecting TEST\Windows\Test Explorer:
Then we're clicking 'Run All' link in the Test Explorer. It finds and runs all test in the solution:
As shown above, our first unit test is passed. Congratulations! A test will fail if testing or tester code is incorrect. Assume that we have forgotten to assign creating task to given person (To test it, comment out the related lines in TaskAppService.CreateTask method). When we run test, it will fail:
Shouldly library makes fail messages clearer. It also makes it easy to write assertions. Compare xUnit's Assert.Equal with Shouldly's ShouldBe extension method:
Assert.Equal(thomasMore.Id, task2.AssignedPersonId); //Using xunit's Assert task2.AssignedPersonId.ShouldBe(thomasMore.Id); //Using Shouldly
I think the second one is more easy and natual to write and read. Shouldly have many other extension methods to make our life easier. See it's documentation.
Testing exceptions
I want to create a second test for the CreateTask method. But, this time with an invalid input:
[Fact] public void Should_Not_Create_Task_Without_Description() { //Description is not set Assert.Throws<AbpValidationException>(() => _taskAppService.CreateTask(new CreateTaskInput())); }
I expect that CreateTask method throws AbpValidationException if I don't set Description for creating task. Because Description property is marked as Required in CreateTaskInput DTO class (see source codes). This test success if CreateTask throws the exception, otherwise fails. Note that; validating input and throwing exception are made by ASP.NET Boilerplate infrastructure.
Using repositories in tests
I'll test to assign a task from one person to another:
//Trying to assign a task of Isaac Asimov to Thomas More [Fact] public void Should_Change_Assigned_People() { //We can work with repositories instead of DbContext var taskRepository = LocalIocManager.Resolve<ITaskRepository>(); //Obtain test data var isaacAsimov = GetPerson("Isaac Asimov"); var thomasMore = GetPerson("Thomas More"); var targetTask = taskRepository.FirstOrDefault(t => t.AssignedPersonId == isaacAsimov.Id); targetTask.ShouldNotBe(null); //Run SUT _taskAppService.UpdateTask( new UpdateTaskInput { TaskId = targetTask.Id, AssignedPersonId = thomasMore.Id }); //Check result taskRepository.Get(targetTask.Id).AssignedPersonId.ShouldBe(thomasMore.Id); }
In this test, I used ITaskRepository to perform database operations, instead of directly working with DbContext. You can use one or mix of these approaches.
Testing async methods
We can also test async methods with xUnit. See the method written to test GetAllPeople method of PersonAppService. GetAllPeople method is async, so, testing method should be also async:
[Fact] public async Task Should_Get_All_People() { var output = await _personAppService.GetAllPeople(); output.People.Count.ShouldBe(4); }
Source Code
You can get the latest source code here https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/SimpleTaskSystem
Summary
In this article, I wanted to show simply testing projects developed upon ASP.NET Boilerplate application framework. ASP.NET Boilerplate provides a good infrastructure to implement test driven development, or simply creating some unit/integration tests for your applications.
Effort library provides a fake database that works well with EntityFramework. It works as long as you use EntityFramework and LINQ to perform database operations. If you have a stored procedure and want to test it, Effort does not work. For such cases, I recommend using LocalDB.