Creating an Application from Scratch using .NET Core and Angular — Part 8
In this article we are going to see how to create the unit tests for the Repository classes in the Infrastructure layer, using an In-Memory database.
The Repository classes are responsible for handle the data into the database. There are at least three ways to create unit tests for these classes, one way is using a real database, which I do not recommend because the tests must be isolated from each other, and to do that it would be necessary to create a new database for each test, but this will make the tests run very slow. Another way is mocking the DbContext, which I also do not recommend. The third way, and the best way in my opinion, is using an In-Memory database provider, this means that it will fully run in memory, which is a lot faster than real database access, and allow us to run these tests in each build. So this approach is the one that we are going to see in this article.
EF Core In-Memory and SQLite In-Memory
Entity Framework Core supports using an In-Memory database provider. This can be used with EF Core database provider or SQLite database provider. There are some differences between these two providers, but the main one, that makes me choose the SQLite, is because the EF Core database provider is not a relational database. So if you choose to use this provider, keep in mind that it will be possible to save data that violates referential integrity constraints, for example, with EF Core database provider it will be possible to remove a category even if some book is using this category. But with the SQLite database provider, you will have this referential integrity constraint, and if you try to remove a category that is being used by some book, it will not be possible. So the SQLite database provider is much closer to a real SQL Server database. I’m using the SQLite provider for the examples on this article, but you can also find on the code on my GitHub (the link is on the end of this article) how to use EF Core provider.
Creating the Project
Let’s start creating a new xUnit project for the Infrastructure layer, similar we did on previous articles (you can read how to create a unit test project on this article). The project name will be “BookStore.Infrastructure.Tests”:
Select the xUnit, add the name of the unit test project and the location of the project (remember to add inside the ‘test’ folder):
It’s necessary to add in the unit test project, the reference for the Infrastructure layer and to the Domain layer. To do it, right-click on Dependencies (in the unit test project) > click in Add Reference and select the BookStore.Domain and BookStore.Infrastructure:
It’s also necessary to install these packages:
Microsoft.Data.Sqlite (if you want to use the SQLite In-Memory provider)
Microsoft.EntityFrameworkCore.Sqlite (if you want to use the SQLite In-Memory provider)Microsoft.EntityFrameworkCore.InMemory (if you want to use the EF Core In-Memory provider)
You can find these packages in the “Manage NuGet packages options”:
Creating the Helper class
To test the Repository classes, we are going to use a Helper class (the class “BookStoreHelperTests”), which will be responsible for creating the database, the connection, add the data in the database and clean the data from the database. This way, the test classes can use this helper class to execute these operations.
The BookStoreHelperTests class, is a helper to the test class, so it will be a static class, and we will start creating the two methods that will be used to create the connection and initialize the database in memory:
On line 11 and 12 we are creating a new instance of the helper “SqliteConnectionStringBuilder” (it’s necessary to include the “using Microsoft.Data.Sqlite”, which is in line 2). A new instance is being created and the DataSource is being set to “memory”, this ensures that the database is created in memory. On line 13 we are creating a new SQLite connectionSQLite using the connectionString. And on line 15 we have the options which will be returned and will be used on the unit tests classes.
On the second method, there is the creation of the database. On line 26 the connection with SQLite is being opened, on line 27 we are ensuring that the database is created, and on line 28 we call the CreateData method to add the data into the in-memory database.
This method is responsible to create the data in the database:
And this is the method responsible to remove all the data from the database:
Because one book must have one category, first is removed all the books, and after that is removed all the categories, this way it will not happen some error related to referential integrity between the data.
Creating the Test classes
On this application, we will have three repository classes:
- Repository — This is an abstract and generic class with the generic methods to execute the basic operation (CRUD and search). All other Repository classes inherit from this base class
- CategoryRepository — This class does not have any specific method, it only uses the methods from the base class (the Repository class)
- BookRepository — This class override some methods from the base class, like GetAll and GetById, and also has two other methods, the GetBooksByCategory and SearchBookWithCategory
To create the tests for these classes, we will have only two classes, the CategoryRepositoryTests, which will be responsible to test the methods from the base class (Repository class) and the BookRepositoryTests, which will be responsible to test only the specific methods from the BookRepository, and this is because once we implement the tests from the base class in the CategoryRepositoryTests, we do not need to test it again on the BookRepositoryTests class.
Let’s start creating the BookRepositoryTests class:
On line 8 there is a private property which is used on the constructor of the unit test class.
On line 12 we are getting the DbContextOptions which will be used to create the database. The code on line 13 calls the method to create the database using the DbContextOptions.
This test class also has one private method, the “CreateBookList”, which is responsible for creating the data that will be used in the tests methods. This way, instead of manually create the default data on the methods that we need to use this data, we can call this method when necessary, avoiding repeating code and making the code cleaner and easier to use:
Now let’s implement the unit tests on the BookRepositoryTests class.
The BookRepository class does not have any private method, this class inherit from the Repository class and implements the methods from the interface IBookRepository. It overrides two methods from the base class and also has other two other methods. So these are the methods that we should test:
Like we already did in the previous tests from the other layers, before we create each test, we need to look at the implementation of each method. On this first example, we are going to create the test for the GetAll method. This is the GetAll method from the BookRepository class:
This method will return all the books from the database, and also includes the category of these books, and order the books by name.
For the GetAll method, we can test if:
- The method returns a list of books when some book exists
- The method returns an empty list of Book when no books exist
- The method returns a list of Book with the correct values when books exist
This is the first test method. This method will test if the GetAll method from the BookRepository class will return a list of Book when some book exist:
Note that for this kind of test, which we are using a database, we need to add the testing code inside of a using statement. This way we can keep the requests from each test independent of other requests. The using statement defines a scope at the end of which an object will be disposed.
On the second test method for the GetAll, we want to validate that the GetAll method returns an empty list of Book when no books exist:
Note that on line 4, there is a call to the CleanDatabase method, which is responsible to delete all the data from the database. This way, when the test makes a call to the database, the books will not be there anymore.
The third test method for GetAll, it will validate if the GetAll method from the BookRepository class will return the expected data:
The data from the “expectedBooks” variable comes from the private method that we have on this test class, so this data is just in memory, and the values that are used to create this object are the same values that we have in the in-memory database. So we expect three books with determined values, and that is what we are validating on this test. Note that on line 18 and 19, we also validate if the object “Category” is with the correct value, and this is because the GetAll method from the BookRepository class includes the Category object in the return.
The next method is the GetById method. Similar to the GetAll method, this method also includes the Category with the searched Book:
For the GetById method, we can test if:
- The method returns the searched book when book the searched Id exists
- The method returns null when book with the searched Id does not exist
- The method returns Book with the correct value when the book with the searched Id exists
These are the implementation of the test methods for the GetById method:
Next is the GetBooksByCategory method. This method searches the books that have a specific category:
For GetBooksByCategory method we can test that:
- The method returns a list of Book when books with searched categoryId exist
- The method returns an empty list when no book with searched categoryId does not exist
- The method return books with searched categoryId when the book with the searched category exists
These are the implementation of the test methods for the GetBooksByCategory method:
Next is the SearchBookWithCategory method. This method searches the books with category with some specific value:
For SearchBookWithCategory method we can test that:
- The method returns one book when one book with searched value exists
- The method returns a list of Book when book with searched value exists
- The method returns an empty list when books with searched value do not exist
These are the implementation of the test methods for the SearchBookWithCategory method:
Testing the CategoryRepository class
The unit test for the BookRepository class is done. Now let’s look at the CategoryRepository class. This class only inherit from the base Repository class:
So what we can test for this class, is actually the methods from the base class, the Repository class, which is an abstract class that is used by other Repository classes.
Testing the Repository class
Because the Repository class is an abstract class, we cannot create an instance of this class. I will explain two approaches to how you can do to test the base class. The first approach is using a real class to test it. Since the CategoryRepository class does not implement any specific method, we can use this class to test the methods of the base class.
The GetAll, the GetById and the Search method, will be similar to the one we create for the BookRepositoryTests class, so I will not add them here on the article (but you can found them in the code on my GitHub, the link is on the end of this article). The Add, Update and Remove method we did not saw it here yet, so let’s focus on these three methods. Let’s first check what these three methods do:
This is the Add method of the Repository class:
This is the Update method of the Repository class:
This is the Remove method of the Repository class:
Note that these methods are similar, they only call the DbSet method (Add, Update and Remove) and call the SaveChanges. So what we need to test on these methods, is that if when these methods are executed, these operations should Add, Update or Remove the entity in the database.
Using Multiple DbContext Instances
On the next unit tests methods, we will use multiple DbContext instances. The reason for that is because we do not want to use the same context to the “Act” and the “Assert”, this way the unit test method will be more realistic.
This is a small explanation about how this works in ASP.NET Core, for example, in one request you get a Category, change some fields and then you send the update, and that’s is another request. When you do that, a new DbContext instance will be created for each request. Is not the same Category that was loaded from the context in the first request that will be returned, but instead of that, it’s an actual call into the database, as the Category has not been loaded on the context from the second request yet.
The lifetime of the context begins when the instance is created and ends when the instance is either disposed or garbage-collected. Use using if you want all the resources that the context controls to be disposed at the end of the block. When you use using, the compiler automatically creates a try/finally block and calls dispose in the finally block.
So let’s create the test for the Add method:
On line 4 is the “Arrange”, where a new Category is being created, this is just an empty object of type Category.
On the first using block, on line 8 a new instance of the CategoryRepository class is being created. On line 9 a category is being added to the variable “categoryToAdd”. On line 11 the Add method is being called, with the categoryToAdd object.
On the second using block, we are using a new DbContext instance to check if the value was added. On line 16 we are getting a category with Id 4, which is being returned to the variable “CategoryResult”.
From line 18 to 21 there is the “Asserts”, where the validation of the test happens.
Next is the Update method:
On this unit test methods, there are three using blocks. On the first one, a category is being returned from the database. On the second block, this category is being updated, and on the third block occurs the validation to check if this updated value was successfully updated.
Next is the Remove method:
Similar to the previous unit test method, there are also three blocks of using. On the first one, a category is being returned from the database, on the second block, the remove method is being called, and on the third block, the Assert is being executed to validate if the result is the expected result.
The other tests methods for the CategoryRepositoryTest class follows the same steps. Since the other unit tests methods are basically the same as the test methods that were explained in this article, I will not add them here, but you can check the complete code on my GitHub (the link is at the end of this article).
Another approach to test the base class
Another approach to test the base class is using an internal class as a concrete class. So let’s create the “RepositoryTests” class, and inside of this class, create an internal class that will be used to inherit from the Repository base class:
The internal concrete class inherit from Repository<Category> (see line 15). We cannot create a fake entity to be used in this class unless we mock the DbSet for this fake entity, but as we are using an In-Memory database, to be as close as possible to a real database, I do not recommend to mock the DbSet. So we need to use a real entity on the tests, that’s why we are using the Category entity.
In the example below, there is an example of a test method to test if a category was successfully added. The test method is similar to the test that we made in the CategoryRepositoryTests class, the only difference from this one, is that instead of creating an instance of CategoryRepository, we are creating an instance of RepositoryConcreteClass (see on line 8 on the code below), which is the internal class that was created to be used in this test class:
On line 16 we are checking the values from the DbSet “Categories”. This test class is testing the methods from the Repository base class, using a real entity (the Category). The unit tests methods for this test class is similar to what we already saw in the previous examples on the CategoryRepositoryTests class, so I will not add on this article all the other test methods for this approach, but you can check the complete code on my GitHub (the link is at the end of this article).
Using an In-Memory database and using a provider like SQLite, makes your tests to be possible to be executed in each build in a fast way, and make your tests much closer to a real database. If you need to test your classes that connect to the database, I recommend using this approach.
You can check the complete code on my GitHub:
Thanks for reading!