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 on this. 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 the following article.

CRUD Operation in Asp.Net Core Web API with Entity Framework Core

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 introduction, let's move to the practical demonstration, how to write CRUD operations Unit Test Cases for Asp.Net Core Web API project.

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

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace CoreServices.Models
{
    public partial class Post
    {
        public int PostId { get; set; }

        [Column(TypeName = "varchar(20)")]
        [Required]
        public string Title { get; set; }
        public string Description { get; set; }
        public int? CategoryId { get; set; }
        public DateTime? CreatedDate { get; set; }

        public Category Category { get; set; }
    }
}

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".

xUnit Testing Project

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.

xUnit Test Project

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 showing with the following image and click to OK.

Crud operation in asp.net core web api

Now, we have added the reference of "CoreServices" project inside the "CoreServices.Test" project. After this point, we can access that 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 purpose, 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 will 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 will use further while running Test Cases.

So, let create a class as "DummyDataDBInitializer" with a "Seed" method, which will first delete your all database tables every time and regenerate tables it based on your Model configurations and add some dummy data. You can take 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.

using CoreServices.Models;
using System;

namespace CoreServices.Test
{
    public class DummyDataDBInitializer
    {
        public DummyDataDBInitializer()
        {
        }

        public void Seed(BlogDBContext context)
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            context.Category.AddRange(
                new Category() { Name = "CSHARP", Slug = "csharp" },
                new Category() { Name = "VISUAL STUDIO", Slug = "visualstudio" },
                new Category() { Name = "ASP.NET CORE", Slug = "aspnetcore" },
                new Category() { Name = "SQL SERVER", Slug = "sqlserver" }
            );
            
            context.Post.AddRange(
                new Post() { Title = "Test Title 1", Description = "Test Description 1", CategoryId = 2, CreatedDate = DateTime.Now },
                new Post() { Title = "Test Title 2", Description = "Test Description 2", CategoryId = 3, CreatedDate = DateTime.Now }
            );
            context.SaveChanges();
        }
    }
}

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

Fluent Assertions is very extensive set of extension methods that allow 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. For more visit https://fluentassertions.com/

 

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

Fluent Assertion

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

xUnit Test Controller

Once PostUnitTestController class will be ready, first will try to access our database where at the runtime, we will seed some dummy data and access those dummy data for testing. So, let 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 only for this demonstration, but you can use some configuration file to keep this information and access this connection string from that configuration file.

public class PostUnitTestController
{
        private PostRepository repository;
        public static DbContextOptions<BlogDBContext> dbContextOptions { get; }
        public static string connectionString = "Server=ABCD;Database=BlogDB;UID=sa;PWD=xxxxxxxxxx;";

        static PostUnitTestController()
        {
            dbContextOptions = new DbContextOptionsBuilder<BlogDBContext>()
                .UseSqlServer(connectionString)
                .Options;
        }

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.

public PostUnitTestController()
{
    var context = new BlogDBContext(dbContextOptions);
    DummyDataDBInitializer db = new DummyDataDBInitializer();
    db.Seed(context);

    repository = new PostRepository(context);

}

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 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 Request etc. 

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

#region Get By Id

        [Fact]
        public async void Task_GetPostById_Return_OkResult()
        {
            //Arrange
            var controller = new PostController(repository);
            var postId = 2;

            //Act
            var data = await controller.GetPost(postId);

            //Assert
            Assert.IsType<OkObjectResult>(data);
        }

        [Fact]
        public async void Task_GetPostById_Return_NotFoundResult()
        {
            //Arrange
            var controller = new PostController(repository);
            var postId = 3;

            //Act
            var data = await controller.GetPost(postId);

            //Assert
            Assert.IsType<NotFoundResult>(data);
        }

        [Fact]
        public async void Task_GetPostById_Return_BadRequestResult()
        {
            //Arrange
            var controller = new PostController(repository);
            int? postId = null;

            //Act
            var data = await controller.GetPost(postId);

            //Assert
            Assert.IsType<BadRequestResult>(data);
        }

        [Fact]
        public async void Task_GetPostById_MatchResult()
        {
            //Arrange
            var controller = new PostController(repository);
            int? postId = 1;

            //Act
            var data = await controller.GetPost(postId);

            //Assert
            Assert.IsType<OkObjectResult>(data);

            var okResult = data.Should().BeOfType<OkObjectResult>().Subject;
            var post = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;

            Assert.Equal("Test Title 1", post.Title);
            Assert.Equal("Test Description 1", post.Description);
        }

#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 passed wrong result and last one for matching the Ok Result Output with our assuming data. 

#region Get All

        [Fact]
        public async void Task_GetPosts_Return_OkResult()
        {
            //Arrange
            var controller = new PostController(repository);

            //Act
            var data = await controller.GetPosts();

            //Assert
            Assert.IsType<OkObjectResult>(data);
        }

        [Fact]
        public void Task_GetPosts_Return_BadRequestResult()
        {
            //Arrange
            var controller = new PostController(repository);

            //Act
            var data = controller.GetPosts();
            data = null;

            if (data != null)
                //Assert
                Assert.IsType<BadRequestResult>(data);
        }

        [Fact]
        public async void Task_GetPosts_MatchResult()
        {
            //Arrange
            var controller = new PostController(repository);

            //Act
            var data = await controller.GetPosts();

            //Assert
            Assert.IsType<OkObjectResult>(data);

            var okResult = data.Should().BeOfType<OkObjectResult>().Subject;
            var post = okResult.Value.Should().BeAssignableTo<List<PostViewModel>>().Subject;

            Assert.Equal("Test Title 1", post[0].Title);
            Assert.Equal("Test Description 1", post[0].Description);

            Assert.Equal("Test Title 2", post[1].Title);
            Assert.Equal("Test Description 2", post[1].Description);
        }

#endregion

Following Unit Test Cases for CREATE operation, let me confirm one thing here that we will prepare the data for adding in Arrange section. One thing, you should notice here 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.

#region Add New Blog

        [Fact]
        public async void Task_Add_ValidData_Return_OkResult()
        {
            //Arrange
            var controller = new PostController(repository);
            var post = new Post() { Title = "Test Title 3", Description = "Test Description 3", CategoryId = 2, CreatedDate = DateTime.Now };

            //Act
            var data = await controller.AddPost(post);

            //Assert
            Assert.IsType<OkObjectResult>(data);
        }

        [Fact]
        public async void Task_Add_InvalidData_Return_BadRequest()
        {
            //Arrange
            var controller = new PostController(repository);
            Post post = new Post() { Title = "Test Title More Than 20 Characteres", Description = "Test Description 3", CategoryId = 3, CreatedDate = DateTime.Now };

            //Act            
            var data = await controller.AddPost(post);

            //Assert
            Assert.IsType<BadRequestResult>(data);
        }

        [Fact]
        public async void Task_Add_ValidData_MatchResult()
        {
            //Arrange
            var controller = new PostController(repository);
            var post = new Post() { Title = "Test Title 4", Description = "Test Description 4", CategoryId = 2, CreatedDate = DateTime.Now };

            //Act
            var data = await controller.AddPost(post);

            //Assert
            Assert.IsType<OkObjectResult>(data);

            var okResult = data.Should().BeOfType<OkObjectResult>().Subject;
            // var result = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;

            Assert.Equal(3, okResult.Value);
        }

#endregion

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

#region Update Existing Blog

        [Fact]
        public async void Task_Update_ValidData_Return_OkResult()
        {
            //Arrange
            var controller = new PostController(repository);
            var postId = 2;

            //Act
            var existingPost = await controller.GetPost(postId);
            var okResult = existingPost.Should().BeOfType<OkObjectResult>().Subject;
            var result = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;

            var post = new Post();
            post.Title = "Test Title 2 Updated";
            post.Description = result.Description;
            post.CategoryId = result.CategoryId;
            post.CreatedDate = result.CreatedDate;

            var updatedData = await controller.UpdatePost(post);

            //Assert
            Assert.IsType<OkResult>(updatedData);
        }

        [Fact]
        public async void Task_Update_InvalidData_Return_BadRequest()
        {
            //Arrange
            var controller = new PostController(repository);
            var postId = 2;

            //Act
            var existingPost = await controller.GetPost(postId);
            var okResult = existingPost.Should().BeOfType<OkObjectResult>().Subject;
            var result = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;

            var post = new Post();
            post.Title = "Test Title More Than 20 Characteres";
            post.Description = result.Description;
            post.CategoryId = result.CategoryId;
            post.CreatedDate = result.CreatedDate;

            var data = await controller.UpdatePost(post);

            //Assert
            Assert.IsType<BadRequestResult>(data);
        }

        [Fact]
        public async void Task_Update_InvalidData_Return_NotFound()
        {
            //Arrange
            var controller = new PostController(repository);
            var postId = 2;

            //Act
            var existingPost = await controller.GetPost(postId);
            var okResult = existingPost.Should().BeOfType<OkObjectResult>().Subject;
            var result = okResult.Value.Should().BeAssignableTo<PostViewModel>().Subject;

            var post = new Post();
            post.PostId = 5;
            post.Title = "Test Title More Than 20 Characteres";
            post.Description = result.Description;
            post.CategoryId = result.CategoryId;
            post.CreatedDate = result.CreatedDate;

            var data = await controller.UpdatePost(post);

            //Assert
            Assert.IsType<NotFoundResult>(data);
        }

#endregion

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

#region Delete Post

        [Fact]
        public async void Task_Delete_Post_Return_OkResult()
        {
            //Arrange
            var controller = new PostController(repository);
            var postId = 2;

            //Act
            var data = await controller.DeletePost(postId);

            //Assert
            Assert.IsType<OkResult>(data);
        }

        [Fact]
        public async void Task_Delete_Post_Return_NotFoundResult()
        {
            //Arrange
            var controller = new PostController(repository);
            var postId = 5;

            //Act
            var data = await controller.DeletePost(postId);

            //Assert
            Assert.IsType<NotFoundResult>(data);
        }

        [Fact]
        public async void Task_Delete_Return_BadRequestResult()
        {
            //Arrange
            var controller = new PostController(repository);
            int? postId = null;

            //Act
            var data = await controller.DeletePost(postId);

            //Assert
            Assert.IsType<BadRequestResult>(data);
        }

#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 link. It will start executing your all Unit Test Cases one by one and final output will be as below.

Unit Test Result in Test Explorer

 

Conclusion

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

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