#26 Creating a Mock Repository
Sunday, April 14, 2019
In this article we will build upon the previous by making it easier to first create mock database sets and then make it easier to add data to them. We will finish up by converting from using hard coded database sets to using a repository which would more closely mirror what would normally be done in a real world project.
Parts
- Part 29: Offset Pager Urls
- Part 28: Offset Pager Start
- Part 27: Mock Context Builder
- Part 26: Mock Repository
- Part 25: Mock Async
- Part 24: Picture Tag Helper
- Part 23: Img DPR Tag Helper
- Part 22: Img Responsive Tag Helper
- Part 21: Img Optimized Display
- Part 20: Img Optimization
- Part 19: Img Lazy Loading
- Part 18: Img Responsive
- Part 17: Bottom Nav
- Part 16: Main Nav Cookie
- Part 15: Main Nav Mobile
- Part 14: Main Nav Search
- Part 13: Main Nav Auth
- Part 12: Main Nav Anchors
- Part 11: Main Nav Logo
- Part 10: Search Results
- Part 9: Search Manager
- Part 8: Search Start
- Part 7: Seeding the Database
- Part 6: Domain Database
- Part 5: Emailing Exceptions
- Part 4: Mailkit
- Part 3: View Renderer
- Part 2: Upgrade to 2.1
- Part 1: Quick Start
Async DbSet Extension Method
- WebUi
- Features
- Mocking
- MockExtensions.cs
- Mocking
- Features
In the last article we saw how we can use the Nuget package Moq to mock up a DbSet
but
we do not want to have to do all of the boilerplate every time we want to create one. The way we are going to make the code
that we wrote reusable is by creating an extension method that will do all of the work for us (a).
MockExtensions.cs
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Moq;
namespace WebUi.Features.Mocking
{
internal static class MockExtensions
{
public static Mock<DbSet<T>> ToAsyncDbSetMock<T>(this IEnumerable<T> source)
where T : class
{
var data = source.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IAsyncEnumerable<T>>()
.Setup(x => x.GetEnumerator())
.Returns(new MockAsyncEnumerator<T>(data.GetEnumerator()));
mockSet.As<IQueryable<T>>()
.Setup(x => x.Provider)
.Returns(new MockAsyncQueryProvider<T>(data.Provider));
mockSet.As<IQueryable<T>>()
.Setup(m => m.Expression)
.Returns(data.Expression);
mockSet.As<IQueryable<T>>()
.Setup(m => m.ElementType)
.Returns(data.ElementType);
mockSet.As<IQueryable<T>>()
.Setup(m => m.GetEnumerator())
.Returns(() => data.GetEnumerator());
return mockSet;
}
}
}
The extension method in our code behind
- WebUi
- Pages
- Mock-Async.cshtml.cs
- Pages
Now that we have the extension method in place we can return to our page code behind and update to make use of it (b). Here I have made a small change from the previous article and that is to use a mock article class instead of a class that was contained in the project from an even farther back article. We are also going to work with two different database sets so that we can hopefully avoid making any assumptions based on just having to deal with one.
Mock-Async.cshtml.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Development.Features.Mocking;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace WebUi.Pages
{
public class MockAsyncModel : PageModel
{
private readonly DbSet<MockAsyncArticle> _articles;
private readonly DbSet<MockAsyncAuthor> _authors;
public MockAsyncModel()
{
var articles = new List<MockAsyncArticle>();
for (var i = 1; i <= 20; i++)
{
articles.Add(new MockAsyncArticle
{
Title = $"Title {i}"
});
}
_articles = articles.ToAsyncDbSetMock().Object;
var authors = new List<MockAsyncAuthor>();
for (var i = 1; i <= 20; i++)
{
authors.Add(new MockAsyncAuthor
{
Name = $"Christopher R. Jamell {i}"
});
}
_authors = authors.ToAsyncDbSetMock().Object;
}
public List<MockAsyncArticle> Articles { get; private set; }
public List<MockAsyncAuthor> Authors { get; private set; }
public async Task OnGet()
{
Articles = await _articles.ToListAsync();
Authors = await _authors.ToListAsync();
}
}
public class MockAsyncArticle
{
public string Title { get; set; }
}
public class MockAsyncAuthor
{
public string Name { get; set; }
}
public class MockAsyncContext : DbContext
{
public DbSet<MockAsyncArticle> Articles { get; set; }
public DbSet<MockAsyncAuthor> Authors { get; set; }
}
public class MockAsyncRepository : IDisposable
{
private readonly MockAsyncContext _context;
public MockAsyncRepository(MockAsyncContext context)
{
_context = context;
}
public DbSet<MockAsyncArticle> Articles => _context.Articles;
public DbSet<MockAsyncAuthor> Authors => _context.Authors;
public void Dispose()
{
_context?.Dispose();
}
}
}
Updating the UI
- WebUi
- Pages
- Mock-Async.cshtml
- Pages
Next up is just a small change to the actual page code so that we can see both the articles and the authors (c).
Mock-Async.cshtml
@page
@model MockAsyncModel
@{
ViewData["Title"] = "Mock Async";
}
<div class="articles">
@foreach (var article in Model.Articles)
{
<div class="article">
<div class="title">@article.Title</div>
</div>
}
</div>
<div class="authors">
@foreach (var author in Model.Authors)
{
<div class="author">
<div class="name">@author.Name</div>
</div>
}
</div>
Generating Data
- WebUi
- Features
- Mocking
- MockExtensions.cs
- Mocking
- Features
If we take a closer look at the code that we added in (b) we can see that we are making use of a couple of for loops to add data to our lists that end up being the source for our database sets. I do not see any reason why the we would not general use the same kind of code to add to other sets as well so we might as well make it easier to do. If you guessed it was time for another helper method you would be correct (d).
MockExtensions.cs
using System;
...
namespace WebUi.Features.Mocking
{
internal static class MockExtensions
{
public static List<T> CreateMockData<T>(int count, Func<int, T> generator)
{
var data = new List<T>();
for (var i = 1; i <= count; i++)
{
data.Add(generator(i));
}
return data;
}
...
}
}
Back to the code behind
- WebUi
- Pages
- Mock-Async.cshtml.cs
- Pages
Once again it is time to use what we have just created to simplify our code behind (e).
Mock-Async.cshtml.cs
...
namespace WebUi.Pages
{
public class MockAsyncModel : PageModel
{
...
public MockAsyncModel()
{
_articles = MockExtensions.CreateMockData(20, x => new MockAsyncArticle
{
Title = $"Title {x}"
}).ToAsyncDbSetMock().Object;
_authors = MockExtensions.CreateMockData(20, x => new MockAsyncAuthor
{
Name = $"Christopher R. Jamell {x}"
}).ToAsyncDbSetMock().Object;
}
...
}
}
Converting to a repository
- WebUi
- Pages
- Mock-Async.cshtml.cs
- Pages
Next step is to remove our database sets and replace them with a repository (f).
Mock-Async.cshtml.cs
...
namespace WebUi.Pages
{
public class MockAsyncModel : PageModel
{
private readonly MockAsyncRepository _repository;
public MockAsyncModel()
{
var context = new MockAsyncContext
{
Articles = MockExtensions.CreateMockData(20, x => new MockAsyncArticle
{
Title = $"Title {x}"
}).ToAsyncDbSetMock().Object,
Authors = MockExtensions.CreateMockData(20, x => new MockAsyncAuthor
{
Name = $"Christopher R. Jamell {x}"
}).ToAsyncDbSetMock().Object
};
_repository = new MockAsyncRepository(context);
}
public List<MockAsyncArticle> Articles { get; private set; }
public List<MockAsyncAuthor> Authors { get; private set; }
public async Task OnGet()
{
Articles = await _repository.Articles.ToListAsync();
Authors = await _repository.Authors.ToListAsync();
}
}
...
}