Creating mock database connections
When working with Entity Framework in a test-driven manner, we need to be able to slip a layer between our last line of code and the framework. This allows us to simulate the database connection without actually hitting the database.
We will be using the NuGet Package Manager to install the Entity Framework Core 1 package, Microsoft.EntityFrameworkCore
. We will also be using a SQL Server database for storing the data, so we will also need Microsoft.EntityFrameworkCore.SqlServer
.
To mock interfaces and base classes, we will use Moq
.
Finally, xunit
is the package we will be using for the unit tests and dotnet-text-xunit
adds tooling support for Visual Studio. Note that the UnitTests
project is a .NET Core App 1.0 (netcoreapp1.0), that Microsoft.EntityFrameworkCore.Design
is configured as a build dependency, and Microsoft.EntityFrameworkCore.Tools
is set as a tool.
Open Using EF Core Solution from the included source code examples.
Execute the database setup script from the code samples included for this recipe. This can be found in the DataAccess
project within the Database
folder.
How to do it…
In the
DataAccess
project, add a new C# interface namedIDbContext
using the following code:using System.Linq; namespace DataAccess { public interface IDbContext { IQueryable<T> Set<T>() where T : class; } }
Add a new unit test in the
UnitTests
project to test so we can supply dummy results for fake database calls with the following code:using System.Linq; using DataAccess; using BusinessLogic; using Moq; using Xunit; namespace UnitTests { public class MockTest : BaseTest { [Fact] public void CanMock() { //Arrange var data = new[] { new Blog { Id = 1, Title = "Title" }, newBlog { Id = 2, Title = "No Title" } }.AsQueryable(); var mock = new Mock<IDbContext>(); mock.Setup(x => x.Set<Blog>()).Returns(data); //Act var context = mock.Object; var blogs = context.Set<Blog>(); //Assert Assert.Equal(data, blogs); } } }
In the
DataAccess
project, update the C# class namedBlogContext
with the following code:using BusinessLogic; using System.Linq; using Microsoft.EntityFrameworkCore; namespace DataAccess { public class BlogContext : DbContext, IDbContext { private readonly string _connectionString; public BlogContext(string connectionString) { _connectionString = connectionString; } public DbSet<Blog> Blogs { get; set; } IQueryable<T> IDbContext.Set<T>() { return base.Set<T>(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(_connectionString); base.OnConfiguring(optionsBuilder); } public void Rollback() { ChangeTracker.Entries().ToList().ForEach(x => { x.State = EntityState.Detached; var keys = GetEntityKey(x.Entity); Set(x.Entity.GetType(), keys); }); } } }
How it works…
We implemented a fake class —a mock—that mimics some of the functionality of our IDbContext
interface that we wish to expose and make testable; in this case, it is just the retrieval of data. This allows us to keep our tests independent of the actual data in the database. Now that we have data available from our mock, we can test whether it acts exactly like we coded it to. Knowing the inputs of the data access code, we can test the outputs for validity. We made our existing BlogContext
class implement the interface where we define the contract that we wish to mock, IDbContext
, and we configured a mock class to return dummy data whenever its Set
method was called.
This layering is accomplished by having a Set
method as an abstraction between the public framework method of Set<T>
and our code, so we can change the type to something constructible. By layering this method, we can now control every return from the database in the test scenarios.
This layering also provides a better separation of concerns, as the DbSet<T>
in Entity Framework mingles multiple independent concerns, such as connection management and querying, into a single object, whereas IQueryable<T>
is the standard .NET interface for performing queries against a data source (DbSet<T>
implements IQueryable<T>
). We will continue to separate these concerns in future recipes.
See also
In this chapter:
Unit testing and mocking