In this tutorial we will learn “Asp.Net Core xUnit Test”. Basically if you want to make sure your application is behaves as you expected, if you want it is work expected to you or as requirement then you need to test it. Unit testing simply verify that some units of codes (functions) are work as expected. It is a good way to test the application code before it goes for production or quality assurance. By unit testing test their behaviors under different condition and data. There are three different testing frameworks for unit testing supported by ASP.NET Core: MSTest, xUnit, and NUnit. In this article I will explain about xUnit framework. xUnit is an open source test framework, the main focus of this framework is on extensibility and flexibility.
In xUnit test framework , we can create a mock object and use it for the unit test. A mock object that can act as a real object but can be controlled in test code. For create mock objects in test code we need a Moq library. A Moq library available in NuGet and also support .NET core.
Asp.Net core applications are tested with different testing frameworks and Entity framework makes testing by using in-memory data provider. If we use In-memory data provider we don’t have necessarily mock or fake context but in this tutorial we will use Mock for one test “GetEmployee” and remaining all tests are without mock. For avoid mocking Entity Framework Core provides us in-memory data provider and it has no Database back-end and is support scenario where persistent data storage is not needed.
Install By Package Manager
- PM> Install-PackageMoq
Install By .Net Core CLI
- dotnet add package Moq
I have already created the project in asp.net core for testing. I will show you the created project files or content so you can understand very well. If you don’t know how to create project in asp.net core then click here. For this project I have used Entity Framework and use only one table from Database because of this article.
Let’s see already created files, database, table, class etc.
Employee (Entity Class)
public class Employee
{
public int ID { get; set; }
public String Name { get; set; }
public String Designation { get; set; }
}
For this article, Employee table is created in Database using Database Management Studio and connect Database to project. When you connect the Database using Edmx then Context file is automatically created and we can make changes in context file, you can see below one interface is create based on context name add DbSet with getter and setter property. If you create project using asp.net core then context file create one parameterized constructor. For use the above employee class with databases and Entity Framework Core we need also context class.
SharedControlsDbContext (Context File)
public interface ISharedControlDbContext
{
DbSet<Employee> Employees { get; set; }
}
public class SharedControlsDbContext : DbContext, ISharedControlDbContext
{
public SharedControlsDbContext(DbContextOptions<SharedControlsDbContext> options) : base(options)
{
}
public DbSet<Employee> Employees { get; set; }
}
An application use our database context with SQL server we should have to add the following code to ConfigureServices() method of Startup class.
services.AddDbContext<SharedControlsDbContext>(options =>
{
options.UseSqlServer (Configuration ["ConnectionStrings: Default"]);
});
The connection string called Default defined in application settings file. For making operation with Database or Business logic probably we could use Repositories. Before make repositories for all we should create one repository context file and here inject all the repositories.
Repository Context
public interface ISharedControlRepositoryContext
{
IEmployeeRepository EmployeeRepository { get; }
}
public class DataBaseSharedRepositoryContext : ISharedControlRepositoryContext
{
private ISharedControlDbContext m_sharedControlsDbContext;
private SharedControlsDbContext _sharedControlsDbContext;
public DataBaseSharedRepositoryContext(ISharedControlDbContext context, SharedControlsDbContext sharedControlsDbContext)
{
m_sharedControlsDbContext = context;
_sharedControlsDbContext = sharedControlsDbContext;
}
public IEmployeeRepository EmployeeRepository => new EmployeeRepository(m_sharedControlsDbContext, _sharedControlsDbContext);
}
In this project I have created only one repository for unit testing purpose. There are 4 functions are available in this repository and we will test this repository functions in unit testing. Basically I have created the interface in all area like context, repository context, and repository.
Repositories (Employee Repository)
public interface IEmployeeRepository
{
IQueryable<Employee> GetEmployees();
Employee GetEmployee(int id);
MessageType AddUpdateEmployee(Employee employee);
MessageType RemoveEmployee(int employeeId);
}
public class EmployeeRepository : IEmployeeRepository
{
private ISharedControlDbContext m_context;
private SharedControlsDbContext _objcontext;
public EmployeeRepository(ISharedControlDbContext context, SharedControlsDbContext objcontext)
{
m_context = context;
_objcontext = objcontext;
}
public IQueryable<Employee> GetEmployees()
{
var employees = m_context.Employees.ToList();
return employees;
}
public Employee GetEmployee(int id)
{
Employee employee = m_context.Employees.Where(a => a.ID == id).FirstOrDefault();
return employee;
}
public MessageType AddUpdateEmployee(Employee employee)
{
try
{
if (employee.ID == 0)
{
_objcontext.Employees.Add(employee);
}
else
{
_objcontext.Entry(employee).State = EntityState.Modified;
}
_objcontext.SaveChanges();
return MessageType.success;
}
catch (Exception ex)
{
return MessageType.error;
}
}
public MessageType RemoveEmployee(int employeeId)
{
try
{
Employee employee = _objcontext.Employees.Where(a => a.ID == employeeId).FirstOrDefault();
_objcontext.Employees.Remove(employee);
_objcontext.SaveChanges();
return MessageType.success;
}
catch (Exception ex)
{
var exs = ex.Message;
return MessageType.error;
}
}
}
Preparing For Testing
Let’s start to create Unit Test to test Employee Repository. First we need to create Unit Test project there you have already created project or you can create new Unit Test project. We will create Unit Test project where already create on project. We went with xUnit as it works also with .NET Core and we can also use it on different platform like Linux and Apple. Right click on solution and Add > New Project > Installed tab > Visual C# > Test > Select Unit Test Project from center panel > Enter project Name > OK.
A Unit Test create one default test class is UnitTest1.cs file. You can add your test in this class or create your own new test class for testing. Create new class “EmployeeTest”. There are 4 tests we will create. In xUnit world method decorated with Fact attribute are tests.
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
When you test the repository may be possible you should make Database related testing or add or remove data from Database. For this point Entity Framework Core provide the facility of without changing anything in database context we can make Entity Framework Core use in-memory data store that is design for testing and other scenarios where we do not need persistent storage for data.
We need database context with test data probably in more than one test we should to create one function GetContextForTest().
public static class GetContextForTest
{
public static SharedControlsDbContext GetContext()
{
var options = new DbContextOptionsBuilder<SharedControlsDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
var context = new SharedControlsDbContext(options);
return context;
}
}
Notice in above code, we have used GUID for in-memory database because of every test run has new database that is not affect to previous test runs.
Repository Tests
[Fact]
public void AddEmployee()
{
var EmployeeData = new Employee { ID = 0, Name = "Test", Designation = "Developer" };
var context = GetContextForTest.GetContext();
var repo = new EmployeeRepository(null, context);
var success = repo.AddUpdateEmployee(EmployeeData);
Assert.Equal(MessageType.success, success);
}
[Fact]
public void UpdateEmployee()
{
var EmployeeData = new Employee { ID = 1, Name = "Test2", Designation = "Tester" };
var context = GetContextForTest.GetContext();
context.Employees.Add(EmployeeData);
context.SaveChanges();
var repo = new EmployeeRepository(null, context);
var success = repo.AddUpdateEmployee(EmployeeData);
Assert.Equal(MessageType.success, success);
}
[Fact]
public void RemoveEmployee()
{
var EmployeeData = new Employee { ID = 1, Name = "Test2", Designation = "Tester" };
var context = GetContextForTest.GetContext();
context.Employees.Add(EmployeeData);
context.SaveChanges();
var repo = new EmployeeRepository(null, context);
var sucessfullyDeleted = repo.RemoveEmployee(1);
Assert.Equal(MessageType.success, sucessfullyDeleted);
}
[Fact]
public void GetEmployee()
{
var EmployeeData = new Employee { ID = 1, Name = "Test" };
Mock<DbSet<Employee>> itemDbSetMock = DbSetMock.Create(EmployeeData);
var mockContext = new Mock<ISharedControlDbContext>();
mockContext.Setup(m => m.Employees).Returns(itemDbSetMock.Object);
var repo = new EmployeeRepository(mockContext.Object, null);
var result = repo.GetEmployee(1);
var data = result;
Assert.NotNull(data);
Assert.Equal(EmployeeData.Name, data.Name);
}
In GetEmployee() test function we have called DbSetMock.Create() function for set the mock object because we need to return data in List, IQueryable or another format. It create DbSet Mock object.
public static class DbSetMock
{
public static Mock<DbSet<T>> Create<T>(params T[] elements) where T : class
{
return new List<T>(elements).AsDbSetMock();
}
}
public static class ListExtensions
{
public static Mock<DbSet<T>> AsDbSetMock<T>(this List<T> list) where T : class
{
IQueryable<T> queryableList = list.AsQueryable();
Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>();
dbSetMock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(queryableList.Provider);
dbSetMock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(queryableList.Expression);
dbSetMock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
dbSetMock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(queryableList.GetEnumerator());
dbSetMock.As<IAsyncEnumerable<T>>().Setup(m => m.GetEnumerator()).Returns(new TestAsyncEnumerator<T>(queryableList.GetEnumerator()));
dbSetMock.As<IQueryable<T>>().Setup(m => m.Provider).Returns(new TestAsyncQueryProvider<T>(queryableList.Provider));
return dbSetMock;
}
}
internal class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal TestAsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
return new TestAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression)
{
return new TestAsyncEnumerable<TResult>(expression);
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class TestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T>
{
public TestAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ }
public TestAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IAsyncEnumerator<T> GetEnumerator()
{
return new TestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IQueryProvider IQueryable.Provider
{
get { return new TestAsyncQueryProvider<T>(this); }
}
}
internal class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public TestAsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public T Current
{
get
{
return _inner.Current;
}
}
public Task<bool> MoveNext(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
}
Run Tests
Now run the tests, for run the tests right click on test project and select the debug. Visual studio open test runner and run all the tests or run test one by one. In test explorer you can see how much it took for particular test. Basically unit tests run very fast. It does not take more than milliseconds. If the test is failed in some cases then test explorer show you failed test with exception or reason. A test lists are show by its test function name.
Bohot Badhiya Master Ji :)
ReplyDelete