CRUD Operations Unit Testing In ASP.NET Core Web API With xUnit

This article will demonstrate how to write Unit Test Cases for CRUD operations in ASP.NET Core Web API with xUnit project. In this demonstration, we will write the Unit Test Cases for CRUD (CREATE, READ, UPDATE and DELETE) operations. We will write at least 3 different Unit Test Cases for 3 different scenarios. In this demonstration, we will not implement CRUD operation in ASP.NET Core Web API because we have already written one article on this previously. We have used that code which we have written for the previous article. If you are willing to learn how to perform CRUD operations in ASP.NET Core Web API using Entity Framework Core, you can visit here.

If you are willing to download the code for above article, you can download it from GitHub here.

We are writing this article because we haven't got much information about how to write Unit Test Case for CRUD Operations on the internet with step by step information. So, we have decided to write on this topic so that it can help others. Without wasting much time on an introduction, let's move to the practical demonstration of how to write CRUD operations Unit Test Cases for ASP.NET Core Web API project.

First, let's download the project from GitHub as provided in the  link above and open it in Visual Studio (We are using Visual Studio 2017 for this demonstration). Once the project opens, move to the Models folder and open the Post Model and let's modify the existing project "CoreServies" Post entity and make Title column as a required field and define the length of the field as follows. We are modifying this Post entity because we will use it while writing the Unit Test Cases for "Create" and "Update" operations.

  1. using System;  
  2. using System.ComponentModel.DataAnnotations;  
  3. using System.ComponentModel.DataAnnotations.Schema;  
  4.   
  5. namespace CoreServices.Models  
  6. {  
  7.     public partial class Post  
  8.     {  
  9.         public int PostId { get; set; }  
  10.   
  11.         [Column(TypeName = "varchar(20)")]  
  12.         [Required]  
  13.         public string Title { get; set; }  
  14.         public string Description { get; set; }  
  15.         public int? CategoryId { get; set; }  
  16.         public DateTime? CreatedDate { get; set; }  
  17.   
  18.         public Category Category { get; set; }  
  19.     }  
  20. }  

Let's build the CoreServices project and see if everything is fine or not. Once all are green then we can move ahead.

Now, we will create one Test project where we will write the Unit Test Cases. So, right-click on the solution of "CoreServices" project and choose Add and then choose "New Project".

CRUD Operations Unit Testing In ASP.NET Core Web API With xUnit 

The next screen will provide lots of options to add a new project, but you have to move ".Net Core" inside the Installed template and then choose "xUnit Test Project (.Net Core)" as you can see with the following image. Just provide the suitable name as per standard, it should be "Project name with Test" (CoreServices.Test) and click to OK.

CRUD Operations Unit Testing In ASP.NET Core Web API With xUnit 

Now, we have the Test Project ready. Let's move and add the references of the Main project (CoreServices) where we have written actual Controller, Repository and Model Classes. Because while writing the Unit Test Cases, we will use these existing Controller, Repository, Model and DbContext instances. 

For adding the project reference, right click on the "Dependencies" of "CoreServices.Test" project and choose "Add References" and then choose "CoreServices" project from the Project > Solution as shown with the following image and click to OK.

CRUD Operations Unit Testing In ASP.NET Core Web API With xUnit 

Now, we have added the reference of "CoreServices" project inside the "CoreServices.Test" project. After this point, we can access the components which are defined in "CoreServices" project. Actually, we are only willing to use Controller and Repository for writing Unit Test Cases, but we will not use the Actual DB. 

For testing purposes, we will create one separate Test DB (BlogDB) with the same name on a different server or with some other name on the same server. For this demonstration, we are using the same database name with some other server. 

Once the database is ready, we have to seed some data into a database before performing testing. So, for that, we will create one class which will be responsible for creating some dummy data which we will use further while running Test Cases.

So, let's create a class as "DummyDataDBInitializer" with a "Seed" method, which will first delete your all database tables every time and regenerate tables based on your Model configurations and add some dummy data. You can get help with following code snippets. Here you can see we are adding some data for "Category" and "Post" tables and then committing it using "context.SaveChanges()" method.

  1. using CoreServices.Models;  
  2. using System;  
  3.   
  4. namespace CoreServices.Test  
  5. {  
  6.     public class DummyDataDBInitializer  
  7.     {  
  8.         public DummyDataDBInitializer()  
  9.         {  
  10.         }  
  11.   
  12.         public void Seed(BlogDBContext context)  
  13.         {  
  14.             context.Database.EnsureDeleted();  
  15.             context.Database.EnsureCreated();  
  16.   
  17.             context.Category.AddRange(  
  18.                 new Category() { Name = "CSHARP", Slug = "csharp" },  
  19.                 new Category() { Name = "VISUAL STUDIO", Slug = "visualstudio" },  
  20.                 new Category() { Name = "ASP.NET CORE", Slug = "aspnetcore" },  
  21.                 new Category() { Name = "SQL SERVER", Slug = "sqlserver" }  
  22.             );  
  23.               
  24.             context.Post.AddRange(  
  25.                 new Post() { Title = "Test Title 1", Description = "Test Description 1", CategoryId = 2, CreatedDate = DateTime.Now },  
  26.                 new Post() { Title = "Test Title 2", Description = "Test Description 2", CategoryId = 3, CreatedDate = DateTime.Now }  
  27.             );  
  28.             context.SaveChanges();  
  29.         }  
  30.     }  
  31. }  

In this demonstration, we will use "Fluent Assertions" for writing a beautiful and user-friendly Unit Test Case. 

Fluent Assertions is a very extensive set of extension methods that allows you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET Framework 4.5 and 4.7, as well as .NET Core 2.0, .NET Standard 1.3, 1.6 and 2.0. 

So, let's open NuGet Package Manager for "CoreServies.Test" project and browse for "FluentAssertions" and installed it.

CRUD Operations Unit Testing In ASP.NET Core Web API With xUnit 

Now, it's time to create a class where we will write the actual Unit Test Cases. So, let's create one class as "PostUnitTestController.cs" in "CoreServices.Test" as follows.

CRUD Operations Unit Testing In ASP.NET Core Web API With xUnit 

Once PostUnitTestController class will be ready, first we will try to access our database where at the runtime, we will seed some dummy data and access the dummy data for testing. So, let's prepare the connection string and based on that get the instance of "BlogDBContext".

Note
Here connection string is defined inside the class, that is not a good approach but we are using it only for this demonstration, but you can use some configuration files to keep this information and access this connection string from that configuration file.

  1. public class PostUnitTestController  
  2. {  
  3.         private PostRepository repository;  
  4.         public static DbContextOptions<BlogDBContext> dbContextOptions { get; }  
  5.         public static string connectionString = "Server=ABCD;Database=BlogDB;UID=sa;PWD=xxxxxxxxxx;";  
  6.   
  7.         static PostUnitTestController()  
  8.         {  
  9.             dbContextOptions = new DbContextOptionsBuilder<BlogDBContext>()  
  10.                 .UseSqlServer(connectionString)  
  11.                 .Options;  
  12.         }  

Once we have available the instance of the "BlogDBContext" then we will go to get the instance of the actual repository "PostRepository" based on the instance of "BlogDBContext" as follows inside the "PostUnitTestControler" constructor.

  1. public PostUnitTestController()  
  2. {  
  3.     var context = new BlogDBContext(dbContextOptions);  
  4.     DummyDataDBInitializer db = new DummyDataDBInitializer();  
  5.     db.Seed(context);  
  6.   
  7.     repository = new PostRepository(context);  
  8.   
  9. }  

Now, we have everything like connection string, an instance of BlogDBContext, an instance of PostRepository and all. So, we can move next and write Unit Test Cases for all EndPoints which are defined inside the PostController in "CoreServices" project.

We will write the Unit Test Cases one by one for all the EndPoints. If you are new in Unit Testing and willing to write it then you can go with my article "Getting started with Unit Testing using C# and xUnit" where you will learn more about Unit Testing Pros and Cons and the best way to write Unit Test Cases. 

We need to keep three things while writing the Unit Test Cases and these are Arranging the data, Performing the action and Matching the output (Arrange, Act, Assert).

So, let's first write Unit Test Case for "Get By Id " method as follows. We have multiple Unit Test Cases to test a single method. Each Unit Test Case has it's own responsibility like matching the OK Result, checking for Not Found Result, checking for Bad Requests etc. 

When we talk about Fluent Assertions, we are implementing in "Task_GetPostById_MatchResult" for getting the actual data.

  1. #region Get By Id  
  2.   
  3.         [Fact]  
  4.         public async void Task_GetPostById_Return_OkResult()  
  5.         {  
  6.             //Arrange  
  7.             var controller = new PostController(repository);  
  8.             var postId = 2;  
  9.   
  10.             //Act  
  11.             var data = await controller.GetPost(postId);  
  12.   
  13.             //Assert  
  14.             Assert.IsType<OkObjectResult>(data);  
  15.         }  
  16.   
  17.         [Fact]  
  18.         public async void Task_GetPostById_Return_NotFoundResult()  
  19.         {  
  20.             //Arrange  
  21.             var controller = new PostController(repository);  
  22.             var postId = 3;  
  23.   
  24.             //Act  
  25.             var data = await controller.GetPost(postId);  
  26.   
  27.             //Assert  
  28.             Assert.IsType<NotFoundResult>(data);  
  29.         }  
  30.   
  31.         [Fact]  
  32.         public async void Task_GetPostById_Return_BadRequestResult()  
  33.         {  
  34.             //Arrange  
  35.             var controller = new PostController(repository);  
  36.             int? postId = null;  
  37.   
  38.             //Act  
  39.             var data = await controller.GetPost(postId);  
  40.   
  41.             //Assert  
  42.             Assert.IsType<BadRequestResult>(data);  
  43.         }  
  44.   
  45.         [Fact]  
  46.         public async void Task_GetPostById_MatchResult()  
  47.         {  
  48.             //Arrange  
  49.             var controller = new PostController(repository);  
  50.             int? postId = 1;  
  51.   
  52.             //Act  
  53.             var data = await controller.GetPost(postId);  
  54.   
  55.             //Assert  
  56.             Assert.IsType<OkObjectResult>(data);  
  57.   
  58.             var okResult = data.Should().BeOfType<OkObjectResult>().Subject;  
  59.             var post = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;  
  60.   
  61.             Assert.Equal("Test Title 1", post.Title);  
  62.             Assert.Equal("Test Description 1", post.Description);  
  63.         }  
  64.  
  65. #endregion  

Following are the Unit Test Cases for "Get All" methods. Here we have written 3 Unit Test Cases like one for Ok Result, another for Bad Request Result when the  wrong result is passed and a last one for matching the Ok Result Output with our data. 

  1. #region Get All  
  2.   
  3.         [Fact]  
  4.         public async void Task_GetPosts_Return_OkResult()  
  5.         {  
  6.             //Arrange  
  7.             var controller = new PostController(repository);  
  8.   
  9.             //Act  
  10.             var data = await controller.GetPosts();  
  11.   
  12.             //Assert  
  13.             Assert.IsType<OkObjectResult>(data);  
  14.         }  
  15.   
  16.         [Fact]  
  17.         public void Task_GetPosts_Return_BadRequestResult()  
  18.         {  
  19.             //Arrange  
  20.             var controller = new PostController(repository);  
  21.   
  22.             //Act  
  23.             var data = controller.GetPosts();  
  24.             data = null;  
  25.   
  26.             if (data != null)  
  27.                 //Assert  
  28.                 Assert.IsType<BadRequestResult>(data);  
  29.         }  
  30.   
  31.         [Fact]  
  32.         public async void Task_GetPosts_MatchResult()  
  33.         {  
  34.             //Arrange  
  35.             var controller = new PostController(repository);  
  36.   
  37.             //Act  
  38.             var data = await controller.GetPosts();  
  39.   
  40.             //Assert  
  41.             Assert.IsType<OkObjectResult>(data);  
  42.   
  43.             var okResult = data.Should().BeOfType<OkObjectResult>().Subject;  
  44.             var post = okResult.Value.Should().BeAssignableTo<List<PostViewModel>>().Subject;  
  45.   
  46.             Assert.Equal("Test Title 1", post[0].Title);  
  47.             Assert.Equal("Test Description 1", post[0].Description);  
  48.   
  49.             Assert.Equal("Test Title 2", post[1].Title);  
  50.             Assert.Equal("Test Description 2", post[1].Description);  
  51.         }  
  52.  
  53. #endregion  

The following Unit Test Cases are for CREATE operation, let me confirm one thing here that we will prepare the data for adding in the Arrange section. One thing you should notice here is that in "Task_Add_InvalidData_Return_BadRequest" Unit Test Cases, we are passing more than 20 characters for Title, which is not correct because in Post model, we have defined the size of the Title as 20 characters.

  1. #region Add New Blog  
  2.   
  3.         [Fact]  
  4.         public async void Task_Add_ValidData_Return_OkResult()  
  5.         {  
  6.             //Arrange  
  7.             var controller = new PostController(repository);  
  8.             var post = new Post() { Title = "Test Title 3", Description = "Test Description 3", CategoryId = 2, CreatedDate = DateTime.Now };  
  9.   
  10.             //Act  
  11.             var data = await controller.AddPost(post);  
  12.   
  13.             //Assert  
  14.             Assert.IsType<OkObjectResult>(data);  
  15.         }  
  16.   
  17.         [Fact]  
  18.         public async void Task_Add_InvalidData_Return_BadRequest()  
  19.         {  
  20.             //Arrange  
  21.             var controller = new PostController(repository);  
  22.             Post post = new Post() { Title = "Test Title More Than 20 Characteres", Description = "Test Description 3", CategoryId = 3, CreatedDate = DateTime.Now };  
  23.   
  24.             //Act              
  25.             var data = await controller.AddPost(post);  
  26.   
  27.             //Assert  
  28.             Assert.IsType<BadRequestResult>(data);  
  29.         }  
  30.   
  31.         [Fact]  
  32.         public async void Task_Add_ValidData_MatchResult()  
  33.         {  
  34.             //Arrange  
  35.             var controller = new PostController(repository);  
  36.             var post = new Post() { Title = "Test Title 4", Description = "Test Description 4", CategoryId = 2, CreatedDate = DateTime.Now };  
  37.   
  38.             //Act  
  39.             var data = await controller.AddPost(post);  
  40.   
  41.             //Assert  
  42.             Assert.IsType<OkObjectResult>(data);  
  43.   
  44.             var okResult = data.Should().BeOfType<OkObjectResult>().Subject;  
  45.             // var result = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;  
  46.   
  47.             Assert.Equal(3, okResult.Value);  
  48.         }  
  49.  
  50. #endregion  

Here as follows are the Unit Test Cases for the UPDATE operation. In Unit Test Cases for an Update operation, we first get the Post details based on Post Id and then modify the data and send it for updating.

  1. #region Update Existing Blog  
  2.   
  3.         [Fact]  
  4.         public async void Task_Update_ValidData_Return_OkResult()  
  5.         {  
  6.             //Arrange  
  7.             var controller = new PostController(repository);  
  8.             var postId = 2;  
  9.   
  10.             //Act  
  11.             var existingPost = await controller.GetPost(postId);  
  12.             var okResult = existingPost.Should().BeOfType<OkObjectResult>().Subject;  
  13.             var result = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;  
  14.   
  15.             var post = new Post();  
  16.             post.Title = "Test Title 2 Updated";  
  17.             post.Description = result.Description;  
  18.             post.CategoryId = result.CategoryId;  
  19.             post.CreatedDate = result.CreatedDate;  
  20.   
  21.             var updatedData = await controller.UpdatePost(post);  
  22.   
  23.             //Assert  
  24.             Assert.IsType<OkResult>(updatedData);  
  25.         }  
  26.   
  27.         [Fact]  
  28.         public async void Task_Update_InvalidData_Return_BadRequest()  
  29.         {  
  30.             //Arrange  
  31.             var controller = new PostController(repository);  
  32.             var postId = 2;  
  33.   
  34.             //Act  
  35.             var existingPost = await controller.GetPost(postId);  
  36.             var okResult = existingPost.Should().BeOfType<OkObjectResult>().Subject;  
  37.             var result = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;  
  38.   
  39.             var post = new Post();  
  40.             post.Title = "Test Title More Than 20 Characteres";  
  41.             post.Description = result.Description;  
  42.             post.CategoryId = result.CategoryId;  
  43.             post.CreatedDate = result.CreatedDate;  
  44.   
  45.             var data = await controller.UpdatePost(post);  
  46.   
  47.             //Assert  
  48.             Assert.IsType<BadRequestResult>(data);  
  49.         }  
  50.   
  51.         [Fact]  
  52.         public async void Task_Update_InvalidData_Return_NotFound()  
  53.         {  
  54.             //Arrange  
  55.             var controller = new PostController(repository);  
  56.             var postId = 2;  
  57.   
  58.             //Act  
  59.             var existingPost = await controller.GetPost(postId);  
  60.             var okResult = existingPost.Should().BeOfType<OkObjectResult>().Subject;  
  61.             var result = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;  
  62.   
  63.             var post = new Post();  
  64.             post.PostId = 5;  
  65.             post.Title = "Test Title More Than 20 Characteres";  
  66.             post.Description = result.Description;  
  67.             post.CategoryId = result.CategoryId;  
  68.             post.CreatedDate = result.CreatedDate;  
  69.   
  70.             var data = await controller.UpdatePost(post);  
  71.   
  72.             //Assert  
  73.             Assert.IsType<NotFoundResult>(data);  
  74.         }  
  75.  
  76. #endregion  

Here are the last Unit Test Cases for the DELETE operation as follows.

  1. #region Delete Post  
  2.   
  3.         [Fact]  
  4.         public async void Task_Delete_Post_Return_OkResult()  
  5.         {  
  6.             //Arrange  
  7.             var controller = new PostController(repository);  
  8.             var postId = 2;  
  9.   
  10.             //Act  
  11.             var data = await controller.DeletePost(postId);  
  12.   
  13.             //Assert  
  14.             Assert.IsType<OkResult>(data);  
  15.         }  
  16.   
  17.         [Fact]  
  18.         public async void Task_Delete_Post_Return_NotFoundResult()  
  19.         {  
  20.             //Arrange  
  21.             var controller = new PostController(repository);  
  22.             var postId = 5;  
  23.   
  24.             //Act  
  25.             var data = await controller.DeletePost(postId);  
  26.   
  27.             //Assert  
  28.             Assert.IsType<NotFoundResult>(data);  
  29.         }  
  30.   
  31.         [Fact]  
  32.         public async void Task_Delete_Return_BadRequestResult()  
  33.         {  
  34.             //Arrange  
  35.             var controller = new PostController(repository);  
  36.             int? postId = null;  
  37.   
  38.             //Act  
  39.             var data = await controller.DeletePost(postId);  
  40.   
  41.             //Assert  
  42.             Assert.IsType<BadRequestResult>(data);  
  43.         }  
  44.  
  45. #endregion  

So, we have written a total of 16 Unit Test Cases for CRUD Operations along with "Get All" method. Now it's time to run the Unit Test Cases and see the output. So, running the Unit Test Cases, let's move to Test Explorer and click to Run All links. It will start executing all your Unit Test Cases one by one and the final output will be as below.

CRUD Operations Unit Testing In ASP.NET Core Web API With xUnit 

Conclusion

So, today, we have learned with a practical demonstration how to write Unit Test Cases for Asp.Net Core Web API CRUD operations.

I hope this post will help you. Please leave your feedback using comment which helps me to improve myself for the next post. If you have any doubts please ask your doubts or queries in the comment section and if you like this post, please share it with your friends. Thanks.