I've got a quick tip this week, about tests and a cool feature of C#.

For better or for worse, my team and I often build projects that are large, complex beasts. In all of them, we prefer to use Dependency Injection as a way to construct complex objects.

A simple example of this might be that a given C# class UserService needs a dependency on UserRepository, and so we would write the UserService class like this:

public class UserService
{
    private readonly IUserRepository _userRepo;
    
    public UserService(IUserRepository userRepo)
    {
        _userRepo = userRepo;
    }
}

The real world is never that simple, though.

No matter what architecture we use, what patterns we follow, or how diligent we are with separation of concerns, sometimes we still end up with complex classes that require a bunch of dependencies:

public class UserService
{
    private readonly IGroupRepository _groupRepo;
    private readonly IHistoryRepository _historyRepo;
    private readonly IJobInfoRepository _jobInfoRepo;
    private readonly IPermissionsRepository _permissionsRepo;
    //...
    
    
    public UserService(IGroupRepository groupRepo,
                       IHistoryRepository historyRepo,
                       IJobInfoRepository jobInfoRepo,
                       IPermissionsRepository permissionsRepo
                       /*...*/)
    {
        _groupRepo = groupRepo;
        _historyRepo = historyRepo;
        _jobInfoRepo = jobInfoRepo;
        _permissionsRepo = permissionsRepo;
        //...
    }
}

At a certain point, trying to refactor this down any further produces no viable way to do so, and we are forced to leave it in its complex, seemingly-too-large state.

This gets more annoying once we start writing tests for any given complex class. Sometimes the method being tested would only require one custom mock, and the other dependencies for the tested class could remain being the default mocks.

[Fact]
public void UserService_GetByID_ShouldReturnValid()
{
    //Arrange
    var group = new Group();
    var groupRepo = new Mock<IGroupRepository>().MockGetByID(group);
    var historyRepo = new Mock<IHistoryRepository>();
    var jobInfoRepo = new Mock<IJobInfoRepository>();
    var permissionsRepo = new Mock<IPermissionsRepository>();
    
    var userService = new UserService(groupRepo,
                                      historyRepo,
                                      jobInfoRepo,
                                      permissionsRepo);
                                      
    //Rest of test
}

We started becoming frustrated with this, because it would take many written lines of code just to set up the thing we were trying to test, and most of those lines were just initializing default dependencies.

A little ways into our current project, we realized that the above code could sometimes be refactored into something that was more concise, but still not great:

[Fact]
public void UserService_GetByID_ShouldReturnValid()
{
    //Arrange
    var group = new Group();
    var groupRepo = new MockGroupRepository().MockGetByID(group);
    
    var userService = new UserService(groupRepo,
                                      new Mock<IHistoryRepository>(),
                                      new Mock<IJobInfoRepository>(),
                                      new Mock<IPermissionsRepository>());
                                      
    //Rest of test
}

To be honest, this annoyed me to no end. This is still pretty bad readability! We'd have 40+ tests for a given class, and in every single one of them we had to instantiate the default mocks just to test one single method.

Which seemed stupid, and I was determined to find a better way. I only partially succeeded.

Here's what we did. We made a method that takes every dependency for a testable class, and returns an instance of that class. The method, by default, has a null reference for each dependency, and checks each argument. If the argument is null, the method assigns it as the default mock instance of that parameter. Then, it uses the arguments to construct the testable class.

It's probably better if I just show you the code:

private UserService CreateUserService(MockGroupRepository groupRepo = null,
                                      MockHistoryRepository historyRepo = null,
                                      MockJobInfoRepository jobInfoRepo = null,
                                      MockPermissionsRepository permissionsRepo = null)
{
     if (groupRepo == null) groupRepo = new MockGroupRepository();
     if (historyRepo == null) historyRepo = new MockHistoryRepository();
     if (jobInfoRepo == null) jobInfoRepo = new MockJobInfoRepository();
     if (permissionsRepo == null) 
         permissionsRepo = new MockPermissionsRepository();
     
     return new UserService(groupRepo, 
                            historyRepo, 
                            jobInfoRepo, 
                            permissionsRepo);
}

Now each test could pass only the required mocks as named arguments, and let this method create the default ones:

 [Fact]
public void UserService_GetByID_ShouldReturnValid()
{
    //Arrange
    var group = new Group();
    var groupRepo = new MockGroupRepository().MockGetByID(group);
    
    var userService = CreateUserService(groupRepo: groupRepo);
    
    //Rest of test
}

I admit, this doesn't seem like much when you only consider one test. But when you have 40 tests per class, and 15 classes, named arguments save a lot of keystrokes.

Anyway, that's how we saved a lot of keystrokes with one simple trick. Try it out!

Using Null-Coalescing Assignment

Thanks to commenter Michel below, we can make our assignment code even more concise (if you are using C# 8.0 or higher) by using the null-coalescing assignment operator ??=:

private UserService CreateUserService(MockGroupRepository groupRepo = null,
                                      MockHistoryRepository historyRepo = null,
                                      MockJobInfoRepository jobInfoRepo = null,
                                      MockPermissionsRepository permissionsRepo = null)
{
     groupRepo ??= new MockGroupRepository();
     historyRepo ??= new MockHistoryRepository();
     jobInfoRepo ??= new MockJobInfoRepository();
     permissionsRepo ??= new MockPermissionsRepository();
     
     return new UserService(groupRepo, 
                            historyRepo, 
                            jobInfoRepo, 
                            permissionsRepo);
}

Thanks for the tip Michel!

Happy Coding!