Apprentice Dev Blog: Week 4 - Repositories and Testing

  • Saturday, Jun 11, 2022
Five clear glass jars filles with a transparent liquid and multiple different coloured candied sweets

The cover image for this post is by Girl with red hat

This blog post was written by Alex. Alex is our apprentice for 2022, and will be providing frequent developer logs.


Jamie here - to jump directly to Alex’s log for this week, click here

Today marks the completion of Alex’s fourth week with us, meaning that they are halfway through their apprenticeship.

remember that Alex is NOT their real name

Alex is studying a Digital T-Level course and has joined us for their mandatory eight weeks in industry.

We are incredibly proud to be helping with Alex’s technical education. And as a reminder, I was assigned to be their PM on the project that they are working on and someone to reach out to for help in completing it. The Digital T-Level course that Alex has taken is very comprehensive and covers a number of different development technologies, but there are certain gaps in their knowledge. This is not meant as a negative point towards either the course designers or those who are delivering it, there is simply not enough time to teach all of the things required of a developer in just two short years.

in fact, development is one of the career choices which require a person to be constantly learning new things

This week I wanted to introduce unit testing, mocking (sometimes called faking), and the repository pattern.

As a reminder, we started a project with the following goal in mind:

Let’s start with Framework, then move to modern .NET. And if there’s time, we’ll investigate .NET Maui.

- Jamie

We’re still working with WinForms right now, and that’s primarily because:

  1. Alex has familiarity with WinForms
  2. Modern .NET supports it
  3. (most importantly) there’s 20+ years of documentation and examples out there

Anyone who has had to build a new project will know the pain of not having documentation for their chosen technology. So, for now, we’re sticking with WinForms because there’s 20+ years of documentation, blog posts, Stack Overflow questions, and YouTube videos for Alex to fall back on if I’m not around to help.

One thing which was pointed out by our friend Scott Harden is that:

.NET Maui really is at the bleeding edge, and there isn’t that much documentation out there - and what there is is partially incorrect because it’s so new. My recommendation would be to go WPF first, then .NET Maui.

- Scott

And that’s quite a good point, actually. Especially seeing the number of issues raised against .NET Maui right now

at the time of writing, the number of open issues stands at 1,213

As such, we’ll only migrate the UI to .NET Maui once it’s a little more stable, after stopping by WPF or a web-based technology first.

Anyway, you didn’t come here to read what I have to say. So here is a development log which represents Alex’s fourth week, in their own words:

one note, I’ve edited for presentation not content


Alex’s Devlog

Hi, I’m Alex and am currently studying a Digital T-Level course, am taking my industry experience with RJJ Software, and these are some of the things that I learned this week.

The first thing we did this week was take a look at the project layout. Before we started working this week, my project had the following layout:

A class diagram showing a simplified view of the relationships between the four major components of the system that Alex is building. This image shows that the UserInterface depends on an interface called IBusinessLogic, which is implemented by a class called BusinessLogic. The BusinessLogic class depends on a database context called DbContext.

In the above image, the UserInterface depends on an interface called IBusinessLogic, which is implemented by a class called BusinessLogic. The BusinessLogic class depends on a database context called DbContext. This is a standard nTier architecture design, however it meant that if we wanted to write unit tests for the BusinessLogic, we would need to inject the DbContext class with a real database into our tests.

This is perfectly doable, but an easier way to do this would be to add an extra layer using the Repository Pattern, and inject that. That way, we solve two problems:

  • the BusinesLogic layer no longer depends on the DbContext (and EntityFramework)
  • we can easily mock an IRepository interface

Since we wanted to focus on the S, I, and D parts of SOLID

Jamie here: Single responsibility, Interface segregation, and Dependency inversion

we needed to insert a new interface and class between the BusinessLogic and the DbContext. This would use the repository pattern and look like this:

A class diagram showing a simplified view of the relationships between the four major components of the system that Alex is building. This image shows that the UserInterface depends on an interface called IBusinessLogic, which is implemented by a class called BusinessLogic. The BusinessLogic class depends on an interface called IRepository, which is implemented by a class called Repository. The Repository depends on a database context called DbContext.

This is a very subtle difference, and one which may look like we’re just pushing the database context further down the class hierarchy. And that’s precisely what we’re doing. We need to push the database context as far away from the business logic as we can. This is because the business logic should have no knowledge of how the data is stored - it’s only job is to do business things.

This is part of a approach called Domain-Driven Design (DDD). We aren’t using DDD to build this system, but it’s principles are quite useful in that they help us with our ultimate aim: mock the thing that the business logic uses to write to the database.

Implementing the Repository Pattern

So the first thing I had to do was create a new interface called IRepository with only two methods:

public interface IRepository<T> where T : class
{
    int Add(T item);
    List<T> GetAll();
}

This interface uses a couple of techniques:

This interface will apply to any type T which is a class - this means that it won’t apply to struct types, but we don’t need that. Entity Framework is doing the hard work of converting all of our database rows into classes, so this work perfectly as a generic wrapper around the DbContext.

Jamie here: a potential refactor here would be to have all of our database table classes inherit from a known base class, then change the constraint to that base class. This will reduce the chances of the IRepository being used for something that the DbContext doesn’t know about

The Repository class needs a little wiring up in order to fully implement the IRepository interface. This is what it looks like:

public class Repository<T> : IRepository<T> where T : class
{
    private readonly AlexProjectDbContext _dbContext;
    private DbSet<T> entities;

    public Repository(AlexProjectDbContext dbContext)
    {
        _dbContext = dbContext;
        entities = dbContext.Set<T>();
    }

    public int Add(T item) 
    {
        entities.Add(item);
        return _dbContext.SaveChanges();
    }

    public List<T> GetAll()
    {
        return entities.ToList();
    }
}

Taking this a step at a time:

private readonly AlexProjectDbContext _dbContext;

public Repository(AlexProjectDbContext dbContext)
{
    _dbContext = dbContext;
}

We’ve seen this elsewhere, it’s .NET’s dependency injection working for us. We’ll need to alter the composition root, but this will continue to work well for us. The other private property is a little different:

private DbSet<T> entities;

public Repository(AlexProjectDbContext dbContext)
{
    _dbContext = dbContext;
    entities = dbContext.Set<T>();
}

The entities property will allow us to operate on the dbContext’s datasets for the T constraint. This means that if T where set to SomeAmazingDatabaseClass then the entities would be working with the DbSet<SomeAmazingDatabaseClass> which needs to be exposed as a public property of the underlying DbContext.

This allows us to only ever expose the entities that we need for the repository to work, without ever modifying the underlying DbContext. Which brings us to the O part of SOLID:

  • Open for extension, closed for modification

Jamie here: this isn’t a perfect example of the O principle, but it will work for what Alex is putting across

A little rewiring later, and our BusinessLogic classes start to look like this:

public interface IService
{
    int CreateNewThing(string name, string address /* etc */);
}

public class ImplementsServiceInterface : IService
{
    private readonly IRepository<SomeAmazingDatabaseClass> _repository;
    
    public ImplementsServiceInterface(IRepository<SomeAmazingDatabaseClass> repository)
    {
        _repository = repository;
    }

    public int CreateNewThing(string name, string address /* etc */)
    {
        return _repository.Add(new SomeAmazingDatabaseClass(name, address /* etc */));
    }
// ... methods here
}

Note that we haven’t yet changed the composition root, so this will not compile - even if it wasn’t a trivialised example. So let’s do that now.

Remember that our composition root is at the very start of the program - in the Program.cs. We need to change the way that the services are wired up from:

private static void ConfigureServices(IServiceCollection services, string connectionString)
{
    services
        .AddTransient<StartScreen>()
        .AddTransient<IService, ImplementsServiceInterface>()

        // And this is where we inject the DbContext
        .AddDbContext<AlexProjectDbContext>(options => options.UseSqlServer(connectionString));
}

to:

private static void ConfigureServices(IServiceCollection services, string connectionString)
{
    services
        .AddTransient<StartScreen>()
        .AddTransient<IService, ImplementsServiceInterface>()

        // Inject our IRepository BEFORE the Database Context
        .AddTransient(typeof(IRepository<>), typeof(Repository<>))
        .AddDbContext<AlexProjectDbContext>(options => options.UseSqlServer(connectionString));
}

The line that we’ve added:

.AddTransient(typeof(IRepository<>), typeof(Repository<>))

is doing something very complex. It’s using the typeof operator to get type data about the IRepository and Repository classes WITHOUT the constraint (i.e the where T : class) or the generic rule (i.e. the <T>). This line is saying to the .NET dependency injector:

Whenever I ask for a type which matches the type definition of IRepository, regardless of the constraint, give me a matching Repository with a matching constraint

So if we ask for an IRepository<string> (as a silly example), the .NET dependency injection framework will new up an instance of a Repository<string> class and all of it’s dependencies, and pass that in

Jamie here: again, this is a silly example as you wouldn’t have a DbSet<string>; but it get’s the point across

With all of that in place, and some rewriting of the services, I was able to start looking into writing unit tests and mocking all of the dependencies for the services.

Mocking and Unit Tests

The services found in the Business Logic layer need to be tested, but we don’t want to be injecting a real-life database into the services in order to test them. If we did, then we’d risk polluting the database that the live application points at - and we don’t want a real user of the system finding our test data

Jamie here: it’s more than a little embarrassing to have a user report that there’s a "Testy McTestface" entry in their reports

So for unit testing, we want to mock (sometimes called fake) our dependencies. This is one of the reasons we’ve been developing against interfaces: so that we can swap out the concrete implementation (in our instance the Repository<T> class) for one with methods which we know will return specific data.

This is where moq comes in. moq is a library which allows us to mock the method calls of a given interface

Jamie here: it also works with classes, but I’ve never used it for that

Let’s say that we have an interface which looks like this:

// our trusty old IRepository interface
public interface IRepository<T> where T : class
{
    int Add(T item);
    List<T> GetAll();
}

And that we have a class which makes use of the IRepository, like this:

public class ImplementsServiceInterface : IService
{
    private readonly IRepository<SomeAmazingDatabaseClass> _repository;
    
    public ImplementsServiceInterface(IRepository<SomeAmazingDatabaseClass> repository)
    {
        _repository = repository;
    }

    public int CreateNewThing(string name, string address /* etc */)
    {
        // Do some stuff
        var newDatabaseEntity = new SomeAmazingDatabaseClass(name, address /* etc */);
        // Do some more stuff

        // Call the Add method of the IRepository<SomeAmazingDatabaseClass>
        return _repository.Add(newDatabaseEntity);
    }
}

And that we wanted to test that the CreateNewThing method did what we wanted it to. We could mock the Add() method of the IRepository<SomeAmazingDatabaseClass> and tell it to return a given value based on what is passed into it.

Here’s what I mean (with some inline comments explaining what’s happening):

[Fact]
public void Call_CreateNewThing_Expect_int_With_Specific_Value_Returned()
{
    // Arrange

    // Create some random input data
    var name = Guid.NewGuid().ToString();
    var address = Guid.NewGuid().ToString();

    // Make a note of our expected return value
    var expectedReturnValue = 1;

    // Mock the IRepository<SomeAmazingDatabaseClass>
    // This creates an in-memory facade class which implements the
    // IRepository<SomeAmazingDatabaseClass> interface. But the
    // key thing is that we can control what the methods on the
    // facade can return for given inputs
    var mockedRepo = new Mock<IRepository<SomeAmazingDatabaseClass>>();

    // Here we are setting up the Add method on the facade. We're
    // saying, whenever ANY instance of the SomeAmazingDatabaseClass
    // is passed into the facade's Add method, return the integer value 1
    mockedRepo.Setup(x =>
        x.Add(It.IsAny<SomeAmazingDatabaseClass>)
        ).Returns(expectedReturnValue);

    // We want to test a real instance of the ImplementsServiceInterface
    // class, so we create one and pass in all of it's dependencies.
    // If the class had more than one dependency we would pass them in,
    // but this class only has one dependency - and we're providing the
    // object behind the facade of the IRepository<SomeAmazingDatabaseClass>
    var serviceToTest = new ImplementsServiceInterface(mockedRepo.Object);

    // Act

    // Call the real method on the service that we are testing. The
    // key here is to check that the CreateNewThing method doesn't
    // do anything to the result we expect to get out of it.
    var result = serviceToTest.CreateNewThing(name, address);

    // Assert

    // Check that the result of calling the CreateNewThing method
    // matches what we expected it to be
    Assert.Equal(expectedReturnValue, result);
}

The above code completely exercises the CreateNewThing method on the ImplementsServiceInterface without having to worry about how it’s dependencies work. This helps us to test one specific part (or unit) of the code at a single time.

But what is a “unit”? A unit is a single piece of code, with all of it’s dependencies separated out. Which is why we’re only testing a single method in the above test. Some people think that units should be larger than a single method, and some people think that a single method is big enough. Here’s a blog post by Martin Fowler on what a unit test is an how big the code that it exercises could be. For my purposes, a single method on one of my services should be complete enough to be classed as a unit.

And with that, I spent the majority of the week working on mocking the dependencies of my services and writing unit tests for them. This gives us two things:

  • Confidence that the service does what it should be doing right now
  • Confidence that when we swap the user interface out, we don’t break anything

Jamie again.

I’m really impressed with how much Alex is able to take in and understand. We’re doing quite a lot of high level software engineering stuff here, dealing with a number of high level methods exposed by C# and .NET, and generics and constraints. There’s really only one (big) thing left to do with Alex’s project, and that’s migrate from WinForms to .NET Maui.

Last week, I’d mentioned that our friend Scott Harden said:

.NET Maui really is at the bleeding edge, and there isn’t that much documentation out there - and what there is is partially incorrect because it’s so new. My recommendation would be to go WPF first, then .NET Maui.

- Scott

And that’s still a thought which is very much on my mind. But what I’m hoping we can do is take the wonderfully well written crash course written by Gerald Versluis and the incredibly detailed four hour course by James Montemagno, and build something using both the code that they provide as examples and fall back on their courses to get Alex through the tough parts of building with .NET Maui.

We’ll start next week by building a brand new project with .NET Maui which replicates the UI that Alex has already built, but without the services, repositories, and Dbcontext. Once that’s in place, we’re going to migrate the services, repositories, and DbContext over to the .NET Maui application. And if all goes well, Alex will have a mobile app to go with their desktop app. Oh, and they’ll have a Blazor app, too.

I’m not expecting all of that to happen in a week; in fact, I’m estimating that it will take the majority of the rest of Alex’s time with us because there will likely be pitfalls with using something so early in it’s lifetime. But if they complete the migration to .NET Maui, there’s plenty of holes to plug in the application logic.