Design, Test and Develop like it's heaven on Earth - Part 15

Published on 9/26/2019

We’ve come a fair bit, and we’re ready to deploy something that can show a blog post. Except that we don’t have any blog posts. In this part we’re going under the domain layer to implement a repository.

A repository as an adaptor

We’ve got a well defined database adaptor abstraction already. Through all the previous parts we’ve been adding method definitions to this abstraction in order to fulfill the dependency of the BlogService implementation. Now we just need to implement that abstraction. So we start with a new unit test.

namespace helloserve.com.Test.Repository
{
    [TestClass]
    public class BlogDatabaseAdaptorTests
    {
        [TestMethod]
        public async Task Read_Verify()
        {
            //arrange
            string title = "title";
            IBlogDatabaseAdaptor adaptor = new BlogRepository();

            //act
            Blog result = await adaptor.Read(title);

            //assert
            Assert.IsNotNull(result);
        }
    }
}

Two things you’ll notice here. The sample includes the namespace to indicate that we’ve created a new unit test project specifically for the repository. Secondly, in our arrangement we declare an instance of IBlogDatabaseAdaptor, but we new up an instance of BlogRepository. These names do not align. Or do they? Refer to part 2 for our discussion on how a repository is just a specialized concept of an adaptor.

Here we will be building an actual repository, abstracted by an adaptor. On other cases (as you’ll see later with the social media stuff), we’ll be building clients that are abstracted by an adaptor. So to that end, when dealing with this project on the implementation side of the abstraction, it’s good that we’re explicit in our namespace and class name, that it is a repository.

In order to get the solution to compile now, we need to provide that repository class. We use the default implementation of the adaptor’s interface.

namespace helloserve.com.Repository
{
    public class BlogRepository : IBlogDatabaseAdaptor
    {
        public Task<Blog> Read(string title)
        {
            throw new NotImplementedException();
        }

        public Task Save(Blog blog)
        {
            throw new NotImplementedException();
        }
    }
}

This also lives in a new project, as you can see from the namespace.

Entity Framework

Here we are at a point where we need to start talking directly to the database. Or at least, pretend to be. EF is an (O)object (R)elational (M)apping framework. Essentially, it is just another adaptor that understands how to generate SQL and map to your objects. And with “your objects” I mean the classes that most closely resemble your database tables.

Code First vs Database First

This framework comes in two flavours. With code first you manually define your table representation classes as entities, and you maintain them yourself. The framework then provides a migration mechanism that upgrades your database for you when you deploy, it applies the changes you made to the classes to the schema in the database.

With database first, you typically use some tools to generate all the classes that are already present in an existing database. You maintain your database through DBAs or some other deployment mechanism, and then you regenerate the classes based on the changed database.

Here I’m going to use code first. We start with defining our context. This is placed in another separate project since it is specific to the database, and it is also typically used by more than one repository implementation.

namespace helloserve.com.Database
{
    public class helloserveContext : DbContext
    {
        public helloserveContext(DbContextOptions options) : base(options) { }
    }
}

Now we need to define the Blog entity, that represents the Blogs table.

[Table("Blogs")]
public class Blog
{
    [Required, Key]
    public string Key { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }
    public bool IsPublished { get; set; }
    public DateTime? PublishDate { get; set; }
}

This table representation defines columns for all the functionality we’ve covered in the previous parts. We update our context to expose this table.

public DbSet<Blog> Blogs { get; set; }

So how do we leverage this context class for testing our repository? The simplest way is to provide an abstraction of the database context too, that we can setup with using mocks again.

public interface IhelloserveContext
{
    DbSet<Blog> Blogs { get; set; }
}

And then of course our context implements this interface.

public class helloserveContext : DbContext, IhelloserveContext

Now in our unit test we can create a mock for this, and verify that it was called.

[TestClass]
public class BlogDatabaseAdaptorTests
{
    readonly Mock<IhelloserveContext> _contextMock = new Mock<IhelloserveContext>();

    [TestMethod]
    public async Task Read_Verify()
    {
        //arrange
        string title = "title";
        IBlogDatabaseAdaptor adaptor = new BlogRepository();

        //act
        Blog result = await adaptor.Read(title);

        //assert
        Assert.IsNotNull(result);
        _contextMock.Verify(x => x.Blogs);
    }
}

But this is not enough. The Read() method has to perform something specific, so just checking that it makes a call using the Blog property is not enough. This method has to return a specific blog entry from a collection, based on the title as a key. How do we do test for that?

Advanced mock setup

The general way of thinking here is that your unit test has to provide some sort of collection that the actual code under test then has to operate on, and return a specific item. My preferred method (as discussed in part 3) is to setup my mock explicitly, as opposed to using an in-memory database for example. We setup a call to the Blog property to return a collection.

[TestMethod]
public async Task Read_Verify()
{
    //arrange
    string title = "title";
    var blogs = new List<Database.Entities.Blog>()
    {
        new Database.Entities.Blog() { Key = "key1" },
        new Database.Entities.Blog() { Key = title },
        new Database.Entities.Blog() { Key = "key2" }
    };
    IBlogDatabaseAdaptor adaptor = new BlogRepository();
    _contextMock.SetupGet(x => x.Blogs)
        .Returns(blogs);

    //act
    Blog result = await adaptor.Read(title);

    //assert
    Assert.IsNotNull(result);
    _contextMock.Verify(x => x.Blogs);
}

This looks right, but it doesn’t compile. The property does not return a List<>, it returns a DbSet<>. There is no way to easily convert the former into the latter, but we can provide a mock in a more round-about way.

public static Mock<DbSet<T>> AsDbSetMock<T>(this IEnumerable<T> collection)
    where T : class
{
    var mockSet = new Mock<DbSet<T>>();
    return mockSet;
}

This extension method takes any implementation of IEnumerable<> and converts it into a mock of DbSet<> using the underlying collection object. Now our unit test can change to use this new extension, and assert on the actual return value of the call.

[TestMethod]
public async Task Read_Verify()
{
    //arrange
    string title = "title";
    var blogs = new List<Database.Entities.Blog>()
    {
        new Database.Entities.Blog() { Key = "key1" },
        new Database.Entities.Blog() { Key = title },
        new Database.Entities.Blog() { Key = "key2" }
    };
    IBlogDatabaseAdaptor adaptor = new BlogRepository();
    _contextMock.SetupGet(x => x.Blogs)
        .Returns(blogs.AsDbSetMock().Object);

    //act
    Blog result = await adaptor.Read(title);

    //assert
    Assert.IsNotNull(result);
    _contextMock.Verify(x => x.Blogs);
    Assert.AreEqual(title, result.Key);
}

We need to pass this test, but the implementation of the Read() method is a bit more involved. We can easily find the relevant blog entry in the collection using LINQ, but now we also need to provide a mapper to map from our database entity to the domain model.

First, a mapper

This means we need another unit test to validate the repository mapper configuration. We take a quick detour to set all this up.

namespace helloserve.com.Repository.Mappers
{
    public static partial class Config
    {
        public static IMapper Mapper { get; private set; }

        static Config()
        {
            MapperConfiguration config = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile<BlogConfig>();
            });

            Mapper = config.CreateMapper();
        }
    }


    public static partial class Config
    {
        public class BlogConfig : Profile
        {
            public BlogConfig()
            {
                CreateMap<Database.Entities.Blog, Domain.Models.Blog>();
            }
        }
    }
}
namespace helloserve.com.Test.Repository.Mappers
{
    [TestClass]
    public class ConfigTests
    {
        [TestMethod]
        public void ConfigTests_Valid()
        {
            Config.Mapper.ConfigurationProvider.AssertConfigurationIsValid();
        }
    }
}

This code looks almost exactly the same as the mapper we configured for the web project, except that the source and destination types are different. This passes immediately, because we named the properties (or columns) on the Blog entity the same as on the Blog domain model, so it follows the Automapper convention. We can now complete the implementation of the repository method.

public class BlogRepository : IBlogDatabaseAdaptor
{
    IhelloserveContext _context;

    public BlogRepository(IhelloserveContext context)
    {
        _context = context;
    }

    public async Task<Blog> Read(string title)
    {
        return (await _context.Blogs.SingleOrDefaultAsync(x => x.Key == title)).Map();
    }
}

In order to compile and run the tests, we pass the new mock for the context to the repository class in the test, and run it. It still fails with a NotImplementedException, but it’s not ours. Looking at the test result pane, it appears to be from within the Entity Framework namespace. It’s trying to call some internal execute method. Solving this is non-trivial, and the final solution is an adaption from this StackOverflow answer. Essentially, we have to provide additional fake implementations of some underlying infrastructure. If you’re interested in this detail, please check out the commit on the github repository. It is worth noting that this infrastructure is considered internal to EntityFrameworkCore, but is currently still declared as public. This could change at any time though, which would make it impossible to mock out DbSet like this.

SOLID: S for Single Responsibility

Now that we pass our test, what is there that we can refactor? The repository method implementation currently does two things. It queries the database (through calling into the context), and then it also maps the result. It’s again orchestrating to two different methods. Notice that we had to include the asynchronous call to the database within braces so that the Map() method call can operate on the result directly, instead of on a Task<> of the result. This is then a good candidate for refactoring.

public async Task<Blog> Read(string title)
{
    var blog = await GetBlog(title);
    return blog.Map();
}

private async Task<Database.Entities.Blog> GetBlog(string title)
{
    return await _context.Blogs.SingleOrDefaultAsync(x => x.Key == title);
}

Conclusion

In this part we created our database context and a repository. We saw how the repository fulfills all the requirements of just another adaptor by being the middleman between database entities and domain models through an abstraction. We saw how the mapper code we wrote here was almost a repeat of the web project mapper code. This keeps the project code base consistent and easy to follow, and illustrates the convenience of using a convention based package like Automapper. We also saw how to provide a mock for something that the .NET Core Framework does not provide an abstraction for, by implementing some underlying interfaces. And while it relies on internal infrastructure, for the time being it is enough to allow us to verify our own code.

Note

In .NET Core 3.0 (and EntityFrameworkCore 3.0) a lot of the infrastructure has now been marked as internal and is no longer available to hook into for mocking. In upcoming posts, as I was dealing with the various preview versions of .NET Core 3.0, I was forced into using the in-memory database pattern.