How to tests Entity Framework Core Codes

How to tests Entity Framework Core Codes

xUnit can be easily used to test Entity Framework Core codes. This tutorial will tech how to test CRUD operations code created with Entity Framework Core in xUnit. I will write 8 test methods to cover each and every aspect of testing so make sure you cover this whole tutorial till the end.

The source codes of this tutorial can be downloaded from my GitHub Repository.

Setup of the App and In-Memory database for Testing

In this tutorial, I will be using the same app that I built in last tutorial How to perform Unit Testing with xUnit in ASP.NET Core. This app solution file has 2 projects in .NET 5.0, these are:

1. MyAppT

An ASP.NET Core MVC project.

2. TestingProject

A Class Library (.NET Core) project that has 3 packages installed.

  1. xunit
  2. xunit.runner.visualstudio
  3. Microsoft.NET.Test.Sdk
xunit xunit.runner.visualstudio Microsoft.NET.Test.Sdk

I have also added the reference of MyAppT to the TestingProject. You can do the same thing to your app or just go to my previous tutorial and download the source code from there.

In Memory Database

I am going to prepare an in-memory database so that I don’t have to use the real SQL server during testing. For this install the following 2 packages in the MyAppT project.

  • 1. Microsoft.EntityFrameworkCore
  • 2. Microsoft.EntityFrameworkCore.InMemory
in memory database

The code will still work if you use an SQL Server database. In-memory database makes the setup quick.

With this we can go forward to create the Database Context and Models.

Database Context and Model class

Create a new class called Register.cs inside the Models folder of MyAppT. Its code is given below:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace MyAppT.Models
{
    public class Register
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        [Range(40, 60)]
        public int Age { get; set; }
    }
}

This class will be used by Entity Framework Core to perform CRUD operations in the in-memory database. There are validation attributes applied to the fields of this class, and these are:

  • 1. The Name should be necessary field to fill.
  • 2. The Age should be from 40 to 60 only.

I will later create a test method that will be testing these scenarios to.

Next create the Database Context class inside the same Models folder. Name this class as AppDbContext.cs.

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyAppT.Models
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }
        public DbSet<Register> Register { get; set; }
    }
}

The Register.cs class has been added to the Database Context as a property. So now Entity Framework Core operations can be performed over it.

Next, add Database context as a service in the ConfigureServices() method of Startup.cs class (of MyAppT project) as shown below:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
    services.AddControllersWithViews();
}

This completes the setup and you can now use Entity Framework core to create CRUD fetures.

Creating a Simple CRUD Operations feature in ASP.NET Core

I will need to create a controller where CRUD operations will be performed. It will work as shown by the below given video:

So, create a new controller called RegistrationController.cs inside the Controllers folder of “MyAppT” project. Provide the Database Context object in it’s constructor’s parameter, this will provide the controller with the Database Context object through Dependency Injection. Check the below highlighted code of this class.

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegistrationController : Controller
    {
        private AppDbContext context;
        public RegistrationController(AppDbContext appDbContext)
        {
            context = appDbContext;
        }
    }
}
CREATE record feature

Add “Create” actions to the RegistrationController where users will be able to create records. The create action code is given below.

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegistrationController : Controller
    {
        private AppDbContext context;
        public RegistrationController(AppDbContext appDbContext)
        {
            context = appDbContext;
        }

        public IActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Create(Register register)
        {
            if (ModelState.IsValid)
            {
                context.Add(register);
                await context.SaveChangesAsync();

                return RedirectToAction("Read");
            }
            else
                return View();
        }
    }
}

Next add the Create.cshtml Razor View inside the Views ➤ Register folder. Add the following code to it.

@model Register

@{
    ViewData["Title"] = "Create Record";
}

<h1 class="bg-info text-white">Create Record</h1>
<a asp-action="Read" class="btn btn-secondary">View all</a>

<div asp-validation-summary="All" class="text-danger btn-light"></div>

<form method="post">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input type="text" asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Age"></label>
        <input type="text" asp-for="Age" class="form-control" />
        <span asp-validation-for="Age" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Create</button>
</form>

The create view provides a form to the user. On filling this form the record will be created in the in-memory database.

I have shown this form in the below image:

create view
READ record feature

All the records stored in the in-memory database will be read and shown on the HTML Table in the view. Add Read action method to the controller. It’s code is given below:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegistrationController : Controller
    {
        private AppDbContext context;
        public RegistrationController(AppDbContext appDbContext)
        {
            context = appDbContext;
        }

        //…

        public IActionResult Read()
        {
            var register = context.Register.ToList();
            return View(register);
        }
    }
}

Next, add a view called Read.cshtml inside the Views ➤ Register folder with the following code:

@model List<Register>

@{
    ViewData["Title"] = "Records";
}

<h1 class="bg-info text-white">Records</h1>
<a asp-action="Create" class="btn btn-secondary">Create</a>

<table class="table table-sm table-bordered">
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th>Age</th>
        <th></th>
        <th></th>
    </tr>
    @foreach (Register r in Model)
    {
        <tr>
            <td>@r.Id</td>
            <td>@r.Name</td>
            <td>@r.Age</td>
            <td>
                <a class="btn btn-sm btn-primary" asp-action="Update" asp-route-id="@r.Id">Update</a>
            </td>
            <td>
                <form asp-action="Delete" asp-route-id="@r.Id" method="post">
                    <button type="submit" class="btn btn-sm btn-danger">
                        Delete
                    </button>
                </form>
            </td>
        </tr>
    }
</table>

The read view will show all the records to the users. See below image:

read view

The HTML Table also has 2 columns that allow user to update and delete the records. I will take this topic next.

UPDATE record feature

Users will also need to update the records which can be done by the update action method. It’s code is shown highlighted below:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegistrationController : Controller
    {
        private AppDbContext context;
        public RegistrationController(AppDbContext appDbContext)
        {
            context = appDbContext;
        }

        //…

        public IActionResult Update(int id)
        {
            var pc = context.Register.Where(a => a.Id == id).FirstOrDefault();
            return View(pc);
        }

        [HttpPost]
        public async Task<IActionResult> Update(Register register)
        {
            if (ModelState.IsValid)
            {
                context.Update(register);
                await context.SaveChangesAsync();

                ViewBag.Result = "Success";
            }
            else
                return View(register);
        }
    }
}

Add the update view – Update.cshtml inside the Views ➤ Register folder with the following code:

@model Register

@{
    ViewData["Title"] = "Update Record";
}

<h1 class="bg-info text-white">Update Record</h1>
<a asp-action="Read" class="btn btn-secondary">View all</a>
<h2 class="bg-light">@ViewBag.Result</h2>

<div asp-validation-summary="All" class="text-danger btn-light"></div>

<form method="post">
    <div class="form-group">
        <label asp-for="Id"></label>
        <input type="text" asp-for="Id" disabled class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Name"></label>
        <input type="text" asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Age"></label>
        <input type="text" asp-for="Age" class="form-control" />
        <span asp-validation-for="Age" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Update</button>
</form>

The look of this view is like that of the create view. Recall, in the Read view I have provided Update link against each record, on clicking that link the users will be taken to the update view.

DELETE record feature

Finally create Delete action whose code is given below.

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MyAppT.Models;

namespace MyAppT.Controllers
{
    public class RegistrationController : Controller
    {
        private AppDbContext context;
        public RegistrationController(AppDbContext appDbContext)
        {
            context = appDbContext;
        }

        //...

        [HttpPost]
        public async Task<IActionResult> Delete(int id)
        {
            var pc = context.Register.Where(a => a.Id == id).FirstOrDefault();
            context.Remove(pc);
            await context.SaveChangesAsync();

            return RedirectToAction("Read");
        }
    }
}

Recall that in the Read view I have provided a Delete button against each record. On clicking that button the corresponding record will be deleted.

Testing Controller with xUnit

Finally, we have arrived on the testing part where I will be writing test methods for the RegistrationController that performs CRUD operations with Entity Framework Core.

Start by adding 2 new classes to the TestingProject, one is an abstract class called TestRegistration.cs where all the 8 test methods will be written. The other class named InMemoryTest.cs inherits the abstract class called TestRegistration.cs and provides the in-memory database configurations to it.

The code of InMemoryTest.cs is given below:

using Microsoft.EntityFrameworkCore;
using MyAppT.Models;

namespace TestingProject
{
    public class InMemoryTest : TestRegistration
    {
        public InMemoryTest()
            : base(
                new DbContextOptionsBuilder<AppDbContext>()
                    .UseInMemoryDatabase("TestDatabase")
                    .Options)
        {
        }
    }
}

The code of TestRegistration.cs is given below. In this class the constructor sets the variable called “ContextOptions” with the database configurations which are sent by the constructor of InMemoryTest.cs class.

Seeding the Database

The constructor also calls a method “Seed()” which adds some initial test data to the database. Check the below code to have this understanding.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        #region Seeding
        public TestRegistration(DbContextOptions<AppDbContext> contextOptions)
        {
            ContextOptions = contextOptions;
            Seed();
        }

        protected DbContextOptions<AppDbContext> ContextOptions { get; }

        private void Seed()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                context.Database.EnsureDeleted();
                context.Database.EnsureCreated();

                var one = new Register()
                {
                    Name = "Test One",
                    Age = 40
                };

                var two = new Register()
                {
                    Name = "Test Two",
                    Age = 50
                };

                var three = new Register()
                {
                    Name = "Test Three",
                    Age = 60
                };
                context.AddRange(one, two, three);
                context.SaveChanges();
            }
        }
        #endregion
    }
}

Next, in this TestRegistration.cs class I will write test methods for all the 4 actions which are “Create, Read, Update and Delete” one by one.

Unit Tests for “Create” actions

There are 3 test methods for the Create action. Let us discuss them.

1. Test_Create_GET_ReturnsViewResultNullModel

This will test the Create action of HTTP GET type so make sure that it returns ViewResult and a null model. It’s code is given below.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        #region Seeding
        //…
        #endregion

        [Fact]
        public void Test_Create_GET_ReturnsViewResultNullModel()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                // Arrange
                var controller = new RegistrationController(context);

                // Act
                var result = controller.Create();

                // Assert
                var viewResult = Assert.IsType<ViewResult>(result);
                Assert.Null(viewResult.ViewData.Model);
            }
        }
    }
}

First, I created the object of AppDbContext type.

var context = new AppDbContext(ContextOptions)

Then used it to initialize the RegistrationController and call the Create action method.

// Arrange
var controller = new RegistrationController(context);

// Act
var result = controller.Create();

The 2 test conditions, which I already discussed, are provided in the assert section.

// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewData.Model);
2. Test_Create_POST_InvalidModelState

This test method tests the Create action of HTTP Post type when the model is invalid. In this case the tests make sure that the action method returns ViewResult with a model object. It’s code is given below.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        //…

        [Fact]
        public void Test_Create_POST_InvalidModelState()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                // Arrange
                var r = new Register()
                {
                    Id = 4,
                    Name = "Test Four",
                    Age = 59
                };
                var controller = new RegistrationController(context);
                controller.ModelState.AddModelError("Name", "Name is required");
  
                // Act
                var result = controller.Create(r);

                // Assert
                var viewResult = Assert.IsType<ViewResult>(result);
                Assert.Null(viewResult.ViewData.Model);
            }
        }
    }
}

Here I have created a new Register object by the name of “r”. Then I have made the ModelState invalid by using the below code:

controller.ModelState.AddModelError("Name", "Name is required");

This creates a condition when user tries submitting a form without filling the name field. After that I called the Create action method of POST type.

var result = controller.Create(r);

Finally, there are 2 test cases which tests if the return type is ViewResult and the model is null.

// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Null(viewResult.ViewData.Model);
3. Test_Create_POST_ValidModelState

Next, I write a test method which test the condition when a new Register record is successfully created. Its code is given below.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        //…

        [Fact]
        public async Task Test_Create_POST_ValidModelState()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                // Arrange
                var r = new Register()
                {
                    Id = 4,
                    Name = "Test Four",
                    Age = 59
                };

                var controller = new RegistrationController(context);

                // Act
                var result = await controller.Create(r);

                // Assert
                var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
                Assert.Null(redirectToActionResult.ControllerName);
                Assert.Equal("Read", redirectToActionResult.ActionName);
            }
        }
    }
}

This case is similar to the above test case except that here I did not make the ModelState invalid. Next, I checked if the return type is RedirectToActionResult, value of controller is null, and the action name (where the user is redirected) is “Read”.

var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Read", redirectToActionResult.ActionName); 

Unit Tests for “Read” action

The Read action returns all the Register records. Recall that I added 3 test records when seeding the database. I will be using these test records for performing the testing. The test method code is given below.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        //…

        [Fact]
        public void Test_Read_GET_ReturnsViewResult_WithAListOfRegistrations()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                // Arrange
                var controller = new RegistrationController(context);

                // Act
                var result = controller.Read();

                // Assert
                var viewResult = Assert.IsType<ViewResult>(result);
                var model = Assert.IsAssignableFrom<IEnumerable<Register>>(viewResult.ViewData.Model);
                Assert.Equal(3, model.Count());
            }
        }
    }
}

The only important thing to note here is how the test cases are written. First the testing is performed to verify ViewResult, and then the model is checked to contain an IEnumerable&ltRegister> type. Finally, model count is checked to contain 3 records.

var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<Register>>(viewResult.ViewData.Model);
Assert.Equal(3, model.Count());

Unit Tests for “Update” actions

There are 3 test methods written to tests the Update action methods. Let us add them one by one.

1. Test_Update_GET_ReturnsViewResult_WithSingleRegistration

Here I will test the GET type of Update action. The code of this test method is given below:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        //…

        [Fact]
        public void Test_Update_GET_ReturnsViewResult_WithSingleRegistration()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                // Arrange
                int testId = 2;

                var controller = new RegistrationController(context);

                // Act
                var result = controller.Update(testId);

                // Assert
                var viewResult = Assert.IsType<ViewResult>(result);
                var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
                Assert.Equal(testId, model.Id);
                Assert.Equal("Test Two", model.Name);
                Assert.Equal(50, model.Age);
            }
        }
    }
}

The Update action should have the id of the Register record so that it can fetch that object from the database. I created this scenario by passing a “testId” variable whose id is 2.

var result = controller.Update(testId);

Next, I performed the tests to check if the fetched record matched the values of the 2nd id Register record or not. These codes that perform these testing is given below.

var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
Assert.Equal(testId, model.Id);
Assert.Equal("Test Two", model.Name);
Assert.Equal(50, model.Age);
2. Test_Update_POST_ReturnsViewResult_InValidModelState

Now I test when the model state is invalid. The test method’s code is given below.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        //…

        [Fact]
        public async Task Test_Update_POST_ReturnsViewResult_InValidModelState()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                // Arrange
                int testId = 2;
                var r = new Register()
                {
                    Id = 2,
                    Name = "Test Four",
                    Age = 59
                };
                var controller = new RegistrationController(context);
                controller.ModelState.AddModelError("Name", "Name is required");

                // Act
                var result = await controller.Update(r);

                // Assert
                var viewResult = Assert.IsType<ViewResult>(result);
                var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
                Assert.Equal(testId, model.Id);
            }
        }
    }
}

The most important thing here is to note that I have made the ModelState invalid by the below code:

controller.ModelState.AddModelError("Name", "Name is required");

Then I called the Update action of POST type and passes to it a test register object “r”.

var result = await controller.Update(r);

Then finally the test cases are written for testing the working. These are:

  • a. Verify that the return type is ViewResult.
  • b. Verify that Model is of Register type.
  • c. The model id value should be equal to that of testId. This verifies that the correct record is fetched from the database.
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
Assert.Equal(testId, model.Id);
3. Test_Update_POST_ReturnsViewResult_ValidModelState

The final test method of the Update action of POST type is shown below:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        //…

        [Fact]
        public async Task Test_Update_POST_ReturnsViewResult_ValidModelState()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                // Arrange
                int testId = 2;
                var r = new Register()
                {
                    Id = 2,
                    Name = "Test Four",
                    Age = 59
                };
                var controller = new RegistrationController(context);

                // Act
                var result = await controller.Update(r);

                // Assert
                var viewResult = Assert.IsType<ViewResult>(result);
                var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
                Assert.Equal(testId, model.Id);
                Assert.Equal(r.Name, model.Name);
                Assert.Equal(r.Age, model.Age);
            }
        }
    }
}

Here I did not make the ModelState invalid, so the form submission will yield updation of the record. I then verified if the record is updated correctly or not.

Assert.Equal(testId, model.Id);
Assert.Equal(r.Name, model.Name);
Assert.Equal(r.Age, model.Age);

Unit Tests for “Delete” action

I tested the working of the “Delete” action by the test method called Test_Delete_POST_ReturnsViewResult_InValidModelState(). Its code is given below.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyAppT.Controllers;
using MyAppT.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace TestingProject
{
    public abstract class TestRegistration
    {
        //…

        [Fact]
        public async Task Test_Delete_POST_ReturnsViewResult_InValidModelState()
        {
            using (var context = new AppDbContext(ContextOptions))
            {
                // Arrange
                int testId = 2;

                var controller = new RegistrationController(context);

                // Act
                var result = await controller.Delete(testId);

                // Assert
                var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
                Assert.Null(redirectToActionResult.ControllerName);
                Assert.Equal("Read", redirectToActionResult.ActionName);
            }
        }
    }
}

Here I verify the action redirects to the “Read”action method once the deletion of the record takes place. You can not run the tests in the test explorers. All the tests will pass, as shown by the below image.

ef core xunit tests passes
Conclusion

This completes the tutorial on testing Entity Framework Core codes with xUnit. Use the codes given in this tutorial freely in your project. Also do share this tutorial in your fb and twitter accounts.

SHARE THIS ARTICLE

  • linkedin
  • reddit

ABOUT THE AUTHOR

I am Yogi S. I write DOT NET artciles on my sites hosting.work and yogihosting.com. You can connect with me on Twitter. I hope my articles are helping you in some way or the other, if you like my articles consider buying me a coffee - Buy Me A Coffee

Leave a Reply

Your email address will not be published.

Related Posts based on your interest