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.
Page Contents
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 8.0, these are:
An ASP.NET Core MVC project.
A Class Library project that has 3 packages installed.
I have also added the reference of MyAppT to the TestingProject. You can do the same thing to your app or just download the source codes.
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.
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.
Create a new class called Register.cs inside the Models folder of MyAppT. Its code is given below:
using System.ComponentModel.DataAnnotations;
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:
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;
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 Program.cs class.
builder.Services.AddDbContext<AppDbContext>(optionsBuilder =>
optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
This completes the setup and you can now use Entity Framework core to create CRUD fetures.
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 MyAppT.Models;
namespace MyAppT.Controllers
{
public class RegistrationController : Controller
{
private AppDbContext context;
public RegistrationController(AppDbContext appDbContext)
{
context = appDbContext;
}
}
}
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 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 file inside the Views ➤ Register folder and 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:
All the records stored in the in-memory database will be read and shown on a HTML Table given in the view. Add Read action method to the controller. It’s code is given below:
using Microsoft.AspNetCore.Mvc;
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:
The HTML Table also has 2 columns that allow user to update and delete the records. I will take this topic next.
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 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";
}
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, users will be taken to the update view.
Finally create Delete action whose code is given below.
using Microsoft.AspNetCore.Mvc;
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.
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.
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.EntityFrameworkCore;
using MyAppT.Models;
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.
There are 3 test methods for the Create action. Let us discuss them.
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 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);
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 Xunit;
namespace TestingProject
{
public abstract class TestRegistration
{
//…
[Fact]
public async Task 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 = await 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);
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 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);
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 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<Register> 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());
There are 3 test methods written to tests the Update action methods. Let us add them one by one.
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 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);
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 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:
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<Register>(viewResult.ViewData.Model);
Assert.Equal(testId, model.Id);
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 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);
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 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.
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