Contents
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 the 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 wrote this article. So, I selected 'Include Prerelease' in nuget package manager dialog. Shouldly
: This library makes it 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:
public abstract class SimpleTaskSystemTestBase : AbpIntegratedTestBase<SimpleTaskSystemTestModule>
{
protected SimpleTaskSystemTestBase()
{
UsingDbContext(context => new SimpleTaskSystemInitialDataBuilder().Build(context));
}
protected override void PreInitialize()
{
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 its 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 before ABP initialized). We registered it as Singleton
(for LocalIocContainer
). Thus, same database (and connection) will be used in a test even we create more than one DbContext
in the 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)
{
}
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)
{
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();
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 DbContext
s 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);
}
}
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 grow.
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 realistically.
So, let's create first test CreateTask
method.
public class TaskAppService_Tests : SimpleTaskSystemTestBase
{
private readonly ITaskAppService _taskAppService;
public TaskAppService_Tests()
{
_taskAppService = LocalIocManager.Resolve<ITaskAppService>();
}
[Fact]
public void Should_Create_New_Tasks()
{
var initialTaskCount = UsingDbContext(context => context.Tasks.Count());
var thomasMore = GetPerson("Thomas More");
_taskAppService.CreateTask(
new CreateTaskInput
{
Description = "my test task 1"
});
_taskAppService.CreateTask(
new CreateTaskInput
{
Description = "my test task 2",
AssignedPersonId = thomasMore.Id
});
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 that 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
understands 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 succeeds, 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 tests 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);
task2.AssignedPersonId.ShouldBe(thomasMore.Id);
I think the second one is easier and natual to write and read. Shouldly has many other extension methods to make our life easier. See its 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()
{
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 succeeds 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:
[Fact]
public void Should_Change_Assigned_People()
{
var taskRepository = LocalIocManager.Resolve<ITaskRepository>();
var isaacAsimov = GetPerson("Isaac Asimov");
var thomasMore = GetPerson("Thomas More");
var targetTask = taskRepository.FirstOrDefault(t => t.AssignedPersonId == isaacAsimov.Id);
targetTask.ShouldNotBe(null);
_taskAppService.UpdateTask(
new UpdateTaskInput
{
TaskId = targetTask.Id,
AssignedPersonId = thomasMore.Id
});
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 also be 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 from here.
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 create 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
.
Use the following links for more information on ASP.NET Boilerplate:
Article History
- 2018-02-22
- 2017-06-28
- Upgraded source code to ABP v2.1.3
- 2016-07-19
- Upgraded article and source code for ABP v0.10 release
- 2016-01-07
- Upgraded solution to .NET Framework 4.5.2
- Upgraded Abp to v0.7.7.1
- 2015-06-15
- Updated sample project and article based on latest ABP version
- 2015-02-02
- First publication of the article