Click here to Skip to main content
15,867,835 members
Articles / DevOps / Testing

Unit Testing in C# using xUnit, Entity Framework, Effort and ASP.NET Boilerplate

Rate me:
Please Sign up or sign in to vote.
4.95/5 (38 votes)
21 Feb 2018CPOL7 min read 150.1K   1.1K   85   23
Implemented unit and integration tests on ASP.NET Boilerplate framework using xUnit, Entity Framework, Effort and Shouldly

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:

Solution structrure

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:

C#
/// <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 its dedicated IIocManager. Thus, tests will be isolated from each other.

We should create a module dedicated for tests. It's SimpleTaskSystemTestModule here:

C#
[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:

C#
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:

C#
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 DbContexts 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:

C#
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 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.

C#
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 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:

  1. Arrange: Prepare for the test
  2. Act: Run the SUT (software under test - the actual testing code)
  3. 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:

  1. 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).
  2. Act: We're creating two tasks using TaskAppService.CreateTask method.
  3. 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:

Open Visual Studio Test Explorer

Then we're clicking 'Run All' link in the Test Explorer. It finds and runs all tests in the solution:

Running first unit test using Visual Studio Test Explorer

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:

Failing test

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:

C#
Assert.Equal(thomasMore.Id, task2.AssignedPersonId); //Using xunit's Assert
task2.AssignedPersonId.ShouldBe(thomasMore.Id); //Using Shouldly

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:

C#
[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 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:

C#
//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 also be async:

C#
[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
    • Upgraded to ABP v3.4
  • 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Volosoft
Turkey Turkey
I have started programming at 14 years old using Pascal as hobby. Then I interested in web development (HTML, JavaScript, ASP...) before university.

I graduated from Sakarya University Computer Engineering. At university, I learned C++, Visual Basic.NET, C#, ASP.NET and Java. I partly implemented ARP, IP and TCP protocols in Java as my final term project.

Now, I am working on Windows and web based software development mostly using Microsoft technologies in my own company.

My open source projects:

* ABP Framework: https://abp.io
* jTable: http://jtable.org
* Others: https://github.com/hikalkan

My personal web site:

https://halilibrahimkalkan.com

Comments and Discussions

 
QuestionPerformance of Tests Pin
pretzelss28-Feb-18 2:53
pretzelss28-Feb-18 2:53 
QuestionCan we refer this article for the Latest version of ABP(v1.0)?? Pin
Member 1276979210-Nov-16 22:14
Member 1276979210-Nov-16 22:14 
AnswerRe: Can we refer this article for the Latest version of ABP(v1.0)?? Pin
Halil ibrahim Kalkan28-Jun-17 0:12
Halil ibrahim Kalkan28-Jun-17 0:12 
QuestionExcellent explanation Pin
Sampath Lokuge6-Jan-16 0:13
Sampath Lokuge6-Jan-16 0:13 
AnswerRe: Excellent explanation Pin
Halil ibrahim Kalkan6-Jan-16 0:57
Halil ibrahim Kalkan6-Jan-16 0:57 
QuestionUsing your solution with an edmx file that is mapped to a SQL Server database Pin
Roger C Moore4-Sep-15 10:47
Roger C Moore4-Sep-15 10:47 
AnswerRe: Using your solution with an edmx file that is mapped to a SQL Server database Pin
Roger C Moore8-Sep-15 5:33
Roger C Moore8-Sep-15 5:33 
QuestionThe 'Microsoft.Bcl.Immutable 1.0.34' package requires NuGet client version '2.8.1' Pin
Roger C Moore4-Sep-15 5:55
Roger C Moore4-Sep-15 5:55 
AnswerRe: The 'Microsoft.Bcl.Immutable 1.0.34' package requires NuGet client version '2.8.1' Pin
Halil ibrahim Kalkan4-Sep-15 8:25
Halil ibrahim Kalkan4-Sep-15 8:25 
GeneralRe: The 'Microsoft.Bcl.Immutable 1.0.34' package requires NuGet client version '2.8.1' Pin
Roger C Moore4-Sep-15 9:36
Roger C Moore4-Sep-15 9:36 
AnswerRe: The 'Microsoft.Bcl.Immutable 1.0.34' package requires NuGet client version '2.8.1' Pin
Roger C Moore8-Sep-15 5:35
Roger C Moore8-Sep-15 5:35 
QuestionEffortException Pin
J Snyman5-Mar-15 20:59
J Snyman5-Mar-15 20:59 
AnswerRe: EffortException Pin
Halil ibrahim Kalkan9-Mar-15 4:01
Halil ibrahim Kalkan9-Mar-15 4:01 
GeneralRe: EffortException Pin
J Snyman10-Mar-15 0:08
J Snyman10-Mar-15 0:08 
QuestionNice Article for ABP Testing Pin
Kadar Mohideen4-Feb-15 5:27
Kadar Mohideen4-Feb-15 5:27 
AnswerRe: Nice Article for ABP Testing Pin
Halil ibrahim Kalkan4-Feb-15 9:05
Halil ibrahim Kalkan4-Feb-15 9:05 
GeneralRe: Nice Article for ABP Testing Pin
Kadar Mohideen4-Feb-15 22:23
Kadar Mohideen4-Feb-15 22:23 
QuestionGreat solution for integration testing Pin
John Brett2-Feb-15 5:50
John Brett2-Feb-15 5:50 
AnswerRe: Great solution for integration testing Pin
Halil ibrahim Kalkan2-Feb-15 6:16
Halil ibrahim Kalkan2-Feb-15 6:16 
QuestionAuthentication and multitenant Pin
Member 111775192-Feb-15 5:04
Member 111775192-Feb-15 5:04 
AnswerRe: Authentication and multitenant Pin
Halil ibrahim Kalkan2-Feb-15 5:12
Halil ibrahim Kalkan2-Feb-15 5:12 
GeneralCongrats and question Pin
Member 111775192-Feb-15 5:01
Member 111775192-Feb-15 5:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.