A Simple interface for fluently mocking a DbSet

You are testing, right? Have you ever used a mock in your test? When testing a class, a mock allows you to create an object that looks just like an object that your class depends on, but acts in a very specific way that you specify for your test, so that you can test your class completely isolated from the rest of your code. If you’re not familiar with mocks, visit Moq’s quickstart guide to get started.

Now that you know about mocks, let’s look at mocking something a little more complicated. If you’ve ever wanted to unit test a method that uses a DbSet<T> to retrieve data, it can be challenging to figure out how to properly mock the DbSet<T>.

Suppose your code looked like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyDbContext : DbContext
{
// Needs to be virtual so that our mocked item can override it
public virtual DbSet<Customer> Customers { get; set; }
}

public class Repository
{
private MyDbContext context;

public Repository(MyDbContext context)
{

this.context = context;
}

// The method you want to test
public bool FindCustomersByPostalCode(string postalCode)
{

return context.Customers
.Where(c => c.PostalCode == postalCode);
}
}

With Moq for mocking, you might start by creating a mocked object:

1
2
var mockedContext = new Mock<MyDbContext>();
mockedContext.Setup(c => c.Customers).Returns( ??? );

But how do you set it up? There are 3 properties and a method you need to mock inside a DbSet<T> to properly mock it: Provider, Expression, ElementType, and GetEnumerator(). I put together the following extension methods to make it easy to mock a DbSet<T>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public static class DbSetMocking
{
private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> data)
where T : class
{
var queryableData = data.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IQueryable<T>>().Setup(m => m.Provider)
.Returns(queryableData.Provider);
mockSet.As<IQueryable<T>>().Setup(m => m.Expression)
.Returns(queryableData.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType)
.Returns(queryableData.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator())
.Returns(queryableData.GetEnumerator());
return mockSet;
}

public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
this IReturns<TContext, DbSet<TEntity>> setup,
TEntity[] entities)
where TEntity : class
where TContext : DbContext
{
var mockSet = CreateMockSet(entities.AsQueryable());
return setup.Returns(mockSet.Object);
}

public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
this IReturns<TContext, DbSet<TEntity>> setup,
IQueryable<TEntity> entities)
where TEntity : class
where TContext : DbContext
{
var mockSet = CreateMockSet(entities);
return setup.Returns(mockSet.Object);
}

public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
this IReturns<TContext, DbSet<TEntity>> setup,
IEnumerable<TEntity> entities)
where TEntity : class
where TContext : DbContext
{
var mockSet = CreateMockSet(entities.AsQueryable());
return setup.Returns(mockSet.Object);
}
}

To use this, first create some sample data that the mocked DbSet<T> will return. This data can be in an Array<T>, IQueryable<T>, or anything that implements IEnumerable<T>, such as a List<T>. Then create and setup the mocked DbSet<Customer>. Note the use of the new ReturnsDbSet extension method:

1
2
3
4
5
6
7
8
var fakeCustomers = new Customer[]
{
new Customer() { Name = "George", PostalCode = "01523" },
new Customer() { Name = "Susan", PostalCode = "12345" }
}

var mockedContext = new Mock<MyDbContext>();
mockedContext.Setup(c => c.Customers).ReturnsDbSet(fakeCustomers);

Now your mocked context is ready to be used in your test.

1
2
3
4
5
var repository = new Repository(mockedContext.object);
var result = repository.FindCustomersByPostalCode("01523");

// Using Fluent Assertions
result.Single().Name.Should().Be("George");

References: