C#

Testing Repository Pattern Using Entity Framework

Unit Testing is extremely important for creating robust software. It’s very simple in principle but it can get a little bit tricky when it comes to real-world applications that depend on databases. In this article, we’re going to explore some approaches to testing repository pattern in our ASP.NET Core applications, and we’re going to implement one of them.

To download the source code for this article, you can visit our GitHub repository.

Let’s start. 

Unit Testing and Test Doubles

Unit Testing is the process of writing code to test our code. As the name implies, it treats each part of the application (usually methods) as a unit. In order for a method to be a testable unit, it has to be isolated; meaning it doesn’t have any external dependencies. However, in real-world applications, our methods usually depend on external dependencies, like databases or APIs. In order to tackle that, we need to replace these dependencies with fake objects or doubles, through which we can simulate these external processes. 

Test doubles have many types, the most used ones are Fakes, Stubs, and Mocks. Let’s go over each one.

Fakes are static replacements for external dependencies. For example, if we’re calling some API on some server, we can create a similar API on a local server and call it instead.

Stubs are fake objects where we override the actual implementation with a predefined output. For example, we can configure methods that query the database to return a constant value.

Mocks are more dynamic than the previous two. In mocks we override the actual implementation with another implementation, so for example, instead of querying the database, we can provide some other logic that queries a static data structure.

Now that we have explained some unit testing basics, let’s explore how we can use them in real-world applications.  

Testing Without the Database

Reading and writing from the database are part of almost every method in real-world applications. When we create unit tests for these methods, we want to use a double for the database. We definitely don’t want to read and write the production database.

So, let’s discover some alternatives that we can use as a data source.

Using in-memory SQLite 

SQLite is a lightweight database engine. One of the main purposes of SQLite is to substitute databases during tests or demos. SQLite can be configured to be in memory, instead of a hard disk. In this case, a new database will be created when we open a connection with the database engine. We can use this fake database to substitute our database calls. When we’re done, we close the connection, and the database will be deleted.

Although this achieves a good deal of isolation, since each method is basically dealing with its own database, it is not a favorable option. This is because we’re most probably using a different database engine than the one our code calls. This might lead to some inconsistent results, making our tests inaccurate. Also, this is not unit testing, because we’re relying on an external dependency, even though it’s in-memory.  

Using EF Core in-memory provider 

Similar to SQLite, Entity Framework Core has an in-memory provider. This was originally designed to test EF Core itself, but it’s still can be used to substitute databases. However, this is wouldn’t be our go-to option either. EF Core in-memory provider has the same disadvantages as SQLite. Furthermore, it’s not even a database engine, so we wouldn’t be able to run raw SQL against it, or use transactions.

This is also not considered unit testing, so we won’t be covering it in detail.

Mocking DbContext

Another approach to creating a test double for the database is by mocking DbContext and DbSet. These mock objects should have their own implementation, in which we can avoid calling the actual database. However, this is only doable for non-querying functionalities. We can easily override AddAsync() and SaveChangesAsync() methods, but querying against DbSets is done using LINQ, which are static methods, and therefore cannot be mocked. 

Introducing Repository Layer and Mocking It

The Repository Pattern has been very popular in the last decade. Almost every Domain-Driven Designed application implements it. It’s basically a layer that holds logic responsible for accessing data sources; databases mainly. Having implemented it, we don’t have to call the database directly from our core logic. Instead of depending on an external data source, we call the repository, which is a logic layer and hence very easy to mock.

This is our favorite approach to creating unit tests for our applications, and we’re going to implement an example in a minute. We strongly recommend reading our article about Repository Pattern, it is recommended to check it out if you’re not quite familiar with the pattern.

Creating Unit Tests For a Real-World Application

We are going to create unit tests for an already existing project, you can check out the source code here. We strongly recommend downloading and going over it before proceeding with this article.

Adding the xUnit Testing Project  

Let’s start by creating an xUnit testing project and calling it AccountOwnerServer.Tests. You can use the Visual Studio template or run the command dotnet new xunit –name AccountOwnerServer.Tests.

The created project should have xUnit and xUnit runner installed out of the box. It also has a test class named UnitTest1.cs. Let’s delete it and create a new test class and name it OwnerControllerTests.cs.

We will be using the Moq library for mocking, so let’s install and we’re ready to go. We can do that using NuGet Package Manager, or by running the following command in PowerShell: Install-Package Moq. Let’s also add a project reference of AccountOwnerServer to our testing project, and we’re good to go.

In this example, we have one controller, OwnerController. We’re going to create unit tests for its action methods. So, let’s first go over our controller dependencies:

public class OwnerController : ControllerBase
{
    private ILoggerManager _logger; 
    private IRepositoryWrapper _repository;
    private IMapper _mapper;
    
    public OwnerController(ILoggerManager logger, IRepositoryWrapper repository, IMapper mapper) 
    { 
        _logger = logger; 
        _repository = repository; 
        _mapper = mapper;
    }
...
}

We notice that we have three dependencies: IloggerManagerIRepositoryWrapper, and IMapper. When we go over the code we find out that the logger and the mapper don’t interact with any external dependencies, so we can use them in our tests without violating the isolation of our tests. However, the _repositoryWrapper calls the database, and therefore we need to mock it.

Mocking IRepositoryWrapper

Let’s create a folder “Mocks” in our testing project. This folder will contain static classes responsible for configuring the mocks that we need in our tests. We create a class named MockIRepositoryWrapper

internal class MockRepositoryWrapper
{
    public static Mock<IRepositoryWrapper> GetMock()
    {
        var mock = new Mock<IRepositoryWrapper>();

        // Setup the mock

        return mock;
    }
}

This static class has one static method GetMock() that’s responsible for creating the IRepositoryWrapper mock, setting it up (we will), and returning the mock. Now, we need to set up the mock by overriding the actual behavior with our mock behavior. The IRepositortWrapper has two properties of types IOwnerRepository and IAccountRepository, and one method Save().

Let’s first override the properties so that they return mocks:

public static Mock<IRepositoryWrapper> GetMock()
{
    var mock = new Mock<IRepositoryWrapper>();

    mock.Setup(m => m.Owner).Returns(() => new Mock<IOwnerRepository>().Object);
    mock.Setup(m => m.Account).Returns(() => new Mock<IAccountRepository>().Object);

    return mock;
}

Here we configure each property to return a mock object. So for example, if we’re testing a method that uses the Owner property, the execution will use this mock object that we just provided. We are currently returning plain mocks, but we actually need to set up these mocks too, we’ll do that in a minute. Let’s now set up the Save() method:

public static Mock<IRepositoryWrapper> GetMock()
{
    var mock = new Mock<IRepositoryWrapper>();

    mock.Setup(m => m.Owner).Returns(() => new Mock<IOwnerRepository>().Object);
    mock.Setup(m => m.Account).Returns(() => new Mock<IAccountRepository>().Object);
    mock.Setup(m => m.Save()).Callback(() => { return; });

    return mock;
}

This method is only responsible for writing to the database, which is outside of our testing scope, so we just want to skip it. So here we’re overriding it with an Action that just returns void

This mock is not quite ready yet, we have to properly mock IOwnerRepository and IAccountRepository and use them in this mock.

Mocking IOwnerRepository 

Let’s create a new class in the “Mocks” folder, named MockIOwnerRepository.cs. Similarly, it contains one static method that sets up a mock and returns it:

internal class MockIOwnerRepository
{
    public static Mock<IOwnerRepository> GetMock()
    {
        var mock = new Mock<IOwnerRepository>();

        var owners = new List<Owner>()
        {
            new Owner()
            {
                Id = Guid.Parse("0f8fad5b-d9cb-469f-a165-70867728950e"),
                Name = "John",
                DateOfBirth = DateTime.Now.AddYears(-20),
                Accounts = new List<Account>()
                {
                    new Account()
                    {
                        Id = new Guid(),
                        AccountType = "",
                        DateCreated = DateTime.Now
                    }
                }
            }
        };

        // Set up
      
        return mock;
    }
}

This mock has one addition. The owners variable, which is just a fake list of owners. We’re going to use this list as our database table double. 

Now, IOwnerRepository has a bunch of methods, and we need to set up each one of them, we’ll do them one by one:

mock.Setup(m => m.GetAllOwners())
    .Returns(() => owners);

Here, we’re overriding GetAllOwners() method, so that when we test a method that calls it, our fake list returns instead of actual data from the database:

mock.Setup(m => m.GetOwnerById(It.IsAny<Guid>()))
    .Returns((Guid id) => owners.FirstOrDefault(o => o.Id == id));

We’re overriding GetOwnerById(), but this method takes up a parameter of type Guid. Here’s when things become more complicated, so let’s explain this line in detail.

The It.IsAny<Guid>() method matches the argument type of the method, which basically means that when this method is called with any argument of type Guid, the following Func will run instead. This is the Func that we passed to the Returns() method, it takes an id as a parameter and returns the first occurrence that matches this id from owners, or null:

mock.Setup(m => m.GetOwnerWithDetails(It.IsAny<Guid>()))
    .Returns((Guid id) => owners.FirstOrDefault(o => o.Id == id));

Similarly, we set up the GetOwnerWithDetails() method.

Now we’re done with the methods that read from the database. For the methods writing to the database we don’t actually need to provide them with any logic, just skip them as we’ve done with the Save() method:

mock.Setup(m => m.CreateOwner(It.IsAny<Owner>()))
    .Callback(() => { return; });

mock.Setup(m => m.UpdateOwner(It.IsAny<Owner>()))
   .Callback(() => { return; });

mock.Setup(m => m.DeleteOwner(It.IsAny<Owner>()))
   .Callback(() => { return; });

Now, our IOwnerRepository mock is ready. The final class should look like this:

internal class MockIOwnerRepository
{
    public static Mock<IOwnerRepository> GetMock()
    {
        var mock = new Mock<IOwnerRepository>();

        var owners = new List<Owner>()
        {
            new Owner()
            {
                Id = Guid.Parse("0f8fad5b-d9cb-469f-a165-70867728950e"),
                Name = "John",
                DateOfBirth = DateTime.Now.AddYears(-20),
                Accounts = new List<Account>()
                {
                    new Account()
                    {
                        Id = new Guid(),
                        AccountType = "",
                        DateCreated = DateTime.Now
                    }

                }
            }
        };

        mock.Setup(m => m.GetAllOwners()).Returns(() => owners);

        mock.Setup(m => m.GetOwnerById(It.IsAny<Guid>()))
            .Returns((Guid id) => owners.FirstOrDefault(o => o.Id == id));

        mock.Setup(m => m.GetOwnerWithDetails(It.IsAny<Guid>()))
            .Returns((Guid id) => owners.FirstOrDefault(o => o.Id == id));

        mock.Setup(m => m.CreateOwner(It.IsAny<Owner>()))
            .Callback(() => { return; });

        mock.Setup(m => m.UpdateOwner(It.IsAny<Owner>()))
           .Callback(() => { return; });

        mock.Setup(m => m.DeleteOwner(It.IsAny<Owner>()))
           .Callback(() => { return; });

        return mock;
    }
}

Mocking IAccountRepository 

Next, let’s do the same process with IAccountRepository:

internal class MockIAccountRepository
{
    public static Mock<IAccountRepository> GetMock()
    {
        var mock = new Mock<IAccountRepository>();

        var accounts = new List<Account>()
        {
            new Account()
            {
                OwnerId = Guid.Parse("0f8fad5b-d9cb-469f-a165-70867728950e")
            }
        };

        mock.Setup(m => m.AccountsByOwner(It.IsAny<Guid>()))
            .Returns((Guid id) => accounts.Where(a => a.OwnerId == id).ToList());

        return mock;
    }
}

We create a mock using Moq, then create a fake list of accounts, set up the AccountsByOwner() method to search our fake list instead of the database, and finally return the mock. 

That’s it, both our repositories are properly mocked. Let’s now use them in the MockIRepositoryWrapper class:

public static Mock<IRepositoryWrapper> GetMock()
{
    var mock = new Mock<IRepositoryWrapper>();
    var ownerRepoMock = MockIOwnerRepository.GetMock();
    var accountRepoMock = MockIAccountRepository.GetMock();

    mock.Setup(m => m.Owner).Returns(() => ownerRepoMock.Object);
    mock.Setup(m => m.Account).Returns(() => accountRepoMock.Object);
    mock.Setup(m => m.Save()).Callback(() => { return; });

    return mock;
}

Creating Tests for GET Methods in AccountController

Now, we’re ready to write tests for the AccountController. As we’ve mentioned, this controller has three dependencies, and we’ve already mocked one of them – the IRepostioryWrapper. The ILogger will just be replaced by a regular instance. However, we have to configure the mapper just the same way we did in the AccountServer project:

public class OwnerControllerTests
{
    public IMapper GetMapper()
    {
        var mappingProfile = new MappingProfile();
        var configuration = new MapperConfiguration(cfg => cfg.AddProfile(mappingProfile));
        return new Mapper(configuration);
    }
}

This method just configures an instance that implements IMapper and returns it. We’re going to use it in our tests. We’ve also added some using statements that we’re going to need in this test class.

Now, let’s create our first test for the GetAllOwners() action method:

[Fact]
public void WhenGettingAllOwners_ThenAllOwnersReturn()
{
    var repositoryWrapperMock = MockIRepositoryWrapper.GetMock();
    var mapper = GetMapper();
    var logger = new LoggerManager();
    var ownerController = new OwnerController(logger, repositoryWrapperMock.Object, mapper);
    
    var result = ownerController.GetAllOwners() as ObjectResult;

    Assert.NotNull(result);
    Assert.Equal(StatusCodes.Status200OK, result.StatusCode);
    Assert.IsAssignableFrom<IEnumerable<OwnerDto>>(result.Value);
    Assert.NotEmpty(result.Value as IEnumerable<OwnerDto>);
}

The first thing we notice is how we name our test methods. Its name should have two parts, separated by an underscore. The first part describes the scenario we are testing, the second part is the expected behavior.

We then start to arrange, creating an object of the controller and passing its dependencies. Next, we act, by calling the method we’re testing, we’re casting the result to ObjectResult so that we can assert the action result contents. Finally, we make some assertions.

Let’s run this test:

And the method passed the test. This method doesn’t have any other case to consider, so this test is just enough.

Now, let’s test the GetOwnerById() method. This method has two cases: it either finds the owner and returns it, or doesn’t find them and returns 404. We should create a test method for each case.

Let’s start with the former:

[Fact]
public void GivenAnIdOfAnExistingOwner_WhenGettingOwnerById_ThenOwnerReturns()
{
    var repositoryWrapperMock = MockIRepositoryWrapper.GetMock();
    var mapper = GetMapper();
    var logger = new LoggerManager();
    var ownerController = new OwnerController(logger, repositoryWrapperMock.Object, mapper);

    var id = Guid.Parse("0f8fad5b-d9cb-469f-a165-70867728950e");
    var result = ownerController.GetOwnerById(id) as ObjectResult;

    Assert.NotNull(result);
    Assert.Equal(StatusCodes.Status200OK, result.StatusCode);
    Assert.IsAssignableFrom<OwnerDto>(result.Value);
    Assert.NotNull(result.Value as OwnerDto);
}

In this test method’s name, we added a new part, which is the ‘Given’ part, which defines the preconditions of the state under test. We’re calling the GetOwnerById() method, passing an id that we know exists, because it exists in the fake list we created in the mock.

Now, let’s test the other case:

[Fact]
public void GivenAnIdOfANonExistingOwner_WhenGettingOwnerById_ThenNotFoundReturns()
{
    var repositoryWrapperMock = MockIRepositoryWrapper.GetMock();
    var mapper = GetMapper();
    var logger = new LoggerManager();
    var ownerController = new OwnerController(logger, repositoryWrapperMock.Object, mapper);

    var id = Guid.Parse("f4f4e3bf-afa6-4399-87b5-a3fe17572c4d");
    var result = ownerController.GetOwnerById(id) as StatusCodeResult;

    Assert.NotNull(result);
    Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode);
}

Here, we’re passing an id that we know doesn’t exist in our fake list, and we just assert that the status code returning is 404. Let’s run these two tests:

Perfect! Everything executes as we’ve expected so far.

Creating Tests for POST Methods of AccountController

We’ve created tests for two GET methods, let’s now try to test a POST one. Let’s check out the CreateOwner() method. 

public IActionResult CreateOwner([FromBody] OwnerForCreationDto owner)
{
    try
    {
        if (owner is null)
        {
            _logger.LogError("Owner object sent from client is null.");
            return BadRequest("Owner object is null");
        }

        if (!ModelState.IsValid)
        {
            _logger.LogError("Invalid owner object sent from client.");
            return BadRequest("Invalid model object");
        }

        var ownerEntity = _mapper.Map<Owner>(owner);

        _repository.Owner.CreateOwner(ownerEntity);
        _repository.Save();

        var createdOwner = _mapper.Map<OwnerDto>(ownerEntity);

        return CreatedAtRoute("OwnerById", new { id = createdOwner.Id }, createdOwner);
    }
    catch (Exception ex)
    {
        _logger.LogError($"Something went wrong inside CreateOwner action: {ex.Message}");
        return StatusCode(500, "Internal server error");
    }
}

As we can see, most of the logic done in this method is for validating the request object. In fact, this is the main thing our server is responsible for, ensuring that valid data is written to the database. So, we want to test the validation functionality, but since this is done by the framework, we have to mock it.

Mocking Model Validation

Let’s create another test class named ValidationTests:

public class ValidationTests
{
    private bool ValidateModel(object model)
    {
        var validationResults = new List<ValidationResult>();
        var ctx = new ValidationContext(model, null, null);
 
        return Validator.TryValidateObject(model, ctx, validationResults, true);
    }
}

We add some using directives that we’re going to need in this class. We also create a method that takes an object and validates it based on its data annotations. This is our mock of the real validation done by the framework. Now, we could have so many cases that we want to test, that our endpoint could receive a number of different invalid objects, so it doesn’t make sense that we write a test method for each case.

Instead, we’re going to use a new type of test method: Theory. A Theory is a method that we can provide with multiple inputs, and for each input, we provide an expected outcome. Eventually, when we run this test method, it runs for each input as a separate test case.  

Let’s see how to do that using XUnit:

[Theory]
[InlineData(null, null, null, false)]
[InlineData(null, "TestAddress", null, false)]
[InlineData(null, null, "06/04/1994", false)]
[InlineData("TestName", null, null, false)]
[InlineData(null, "TestAddress", "06/04/1994", false)]
[InlineData("TestName", null, "06/04/1994", false)]
[InlineData("TestName", "TestAddress", "06/04/1994", true)]
[InlineData("TestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTest", "TestAdress", "06/04/1994", false)]
public void TestModelValidation(string? name, string? address, string? dateOfBirth, bool isValid)
{
    var owner = new OwnerForCreationDto()
    {
        Address = address,
        Name = name,
        DateOfBirth = dateOfBirth is null ? DateTime.MinValue : DateTime.Parse(dateOfBirth)
    };
    
    Assert.Equal(isValid, ValidateModel(owner));
}

A theory method is marked by the [Theory] attribute. And we can provide it with inputs and expected outputs using the [InlineData] attribute. We pass any number of arguments we need (they have to be constants), and the final argument is the expected outcome. In the method, we add matching parameters.

Inside the method, we create an object using the provided values, and we assert its validity:

 

So, although we only wrote one method, we actually created 9 tests. And they all passed, perfect!

Writing Positive Tests for Create Method

Now that we covered the validation part, let’s get back to OwnerControllerTests class and create a successful test for the CreateOwner() method:

[Fact]
public void GivenValidRequest_WhenCreatingOwner_ThenCreatedReturns()
{
    var repositoryWrapperMock = MockIRepositoryWrapper.GetMock();
    var mapper = GetMapper();
    var logger = new LoggerManager();
    var ownerController = new OwnerController(logger, repositoryWrapperMock.Object, mapper);

    var owner = new OwnerForCreationDto()
    {
        Address = "TestAddress",
        Name = "TestName",
        DateOfBirth = new DateTime(1994, 7, 25)
    };

    var result = ownerController.CreateOwner(owner) as ObjectResult;

    Assert.NotNull(result);
    Assert.IsAssignableFrom<CreatedAtRouteResult>(result);
    Assert.Equal((int)HttpStatusCode.Created, result!.StatusCode);
    Assert.Equal("OwnerById", (result as CreatedAtRouteResult)!.RouteName);
}

In the above code, we pass a valid request which should succeed, and we make some assertions on the response.

Now, we don’t need to create any unsuccessful tests for this method, because all the unsuccessful scenarios are being tested by the ValidationTests class.

We won’t go into testing PUT and DELETE methods, but you can try it out and let us know how it went.

Conclusion 

In this article, we’ve discussed different ways of testing a repository pattern without including a database. We’ve implemented this approach on an example API project, and explored different kinds of unit tests.

Code Maze

View Comments

Share
Published by
Code Maze

Recent Posts

Code Maze Weekly #134

Issue #134 of the Code Maze weekly. Check out what's new this week and enjoy…

Updated Date Aug 12, 2022

Heap Sort in C#

In this article, we'll look at the heap sort algorithm in C#. We'll discuss what…

Updated Date Aug 11, 2022

Flags Attribute For Enum in C#

In this article, we are going to learn about the Flags attribute for enum in…

Aug 10, 2022

Code Maze Weekly #133

Issue #133 of the Code Maze weekly. Check out what's new this week and enjoy…

Updated Date Aug 5, 2022

Type Checking and Type Casting in C#

In this article, we are going to learn various ways of converting a value from…

Updated Date Aug 5, 2022

Sort Dictionary by Value in .NET

In this article, we are going to learn how to sort the values in the…

Updated Date Aug 4, 2022