Advertisement

#26 Creating a Mock Repository

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.

Async DbSet Extension Method

  • WebUi
    • Features
      • Mocking
        • MockExtensions.cs

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;
        }
    }
}
(a) Extension method that we can use to convert an IEnumerable to a mock DbSet object that we can use as a data source for our in memory repository.

The extension method in our code behind

  • WebUi
    • Pages
      • Mock-Async.cshtml.cs

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();
        }
    }
}
(b) Updating the code behind to make use of our extension method.
Advertisement

Updating the UI

  • WebUi
    • Pages
      • Mock-Async.cshtml

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>
(c) Updating our UI so that we can see both the articles and the authors.

Generating Data

  • WebUi
    • Features
      • Mocking
        • MockExtensions.cs

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;
        }
        ...
    }
}
(d) New helper method that we can use to generate data by way of using a for loop.

Back to the code behind

  • WebUi
    • Pages
      • Mock-Async.cshtml.cs

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;
        }
        ...
    }
}
(e) Updating the code behind to use our new helper method to generate the data.

Converting to a repository

  • WebUi
    • Pages
      • Mock-Async.cshtml.cs

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();
        }
    }
    ...
}
(f) Instead of dealing directly with many database sets we need to replace them with a repository.
Exciton Interactive LLC
Advertisement