How to write testable code

You Are Here:

How to write testable code

The importance of testable code is not unknown to developers. There are lots of best practices for writing testable code. The main goal for us is to create a maintainable, extensible solution. I do not want to explain the benefits of testable code here. There are a lot of articles out there. In this article, I will try to list the most common practices for testable code.

Many developers think they write good code. But good looking code is not always testable code. Let’s start!


Dependency Injection

Testable code is all about dependencies. The big benefit of using Inversion of Control / Dependency Injection via constructor parameter interfaces is that you easily can mock all those dependencies in your unit tests. Today's generation of software engineers uses dependency injection framework in their applications. Unfortunately, they still violate the inversion of control intentionally or unintentionally in some cases.

        
//Untestable Code
private readonly IProductService _productService;

public ProductController()
{
     _productService = new ProductService();
}
            
        
        
//Testable Code
private readonly IProductService _productService;

public ProductController(IProductService productService)
{
     _productService = productService;
}
            
        


The new Keyword

The new keyword and direct object creation is the worst enemy of unit testing. In the previous example, I already showed how we can replace dependency injection with concrete object creation. Developer sometimes use new objection like this

        
// Untestable code
public string GetConnectionString()
{
     var configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();
     string connectionString = configuration
                .GetValue<string>("ConnectionString");
     return connectionString;
}
            
        
        
// Testable code
// 1. Create an interface
public interface IAppSettings
{
     string ConnectionString { get; set; }
}

// 2. Inject IAppSettings to your constructor
private readonly IAppSettings _appSettings;

public ProductController( IAppSettings appSettings)
{
     _appSettings = appSettings;
}

// 3. Update your method
public string GetConnectionString()
{
     string connectionString = _appSettings.ConnectionString;
     return connectionString;
}
            
        


Static

One of the principles of a unit test is that it must have full control of the system under test. The best solution is to avoid static classes and methods in your code at all.

        
// Untestable code
public double GetDiscountedPrice(double price, DateTime lastOfferTime)
{
     if (lastOfferTime > DateTime.UtcNow)
          return price * 0.25;

     return price;
}
            
        
        
// Testable code
// 1. Create an interface
public interface IDateTimeService
{
        DateTime Now();
}

// 2. Inject IDateTimeService to constructor
private readonly IDateTimeService _dateTimeService;

public ArticleService(IDateTimeService dateTimeService)
{
     _dateTimeService = dateTimeService;
}

// 3. Update your method
public double GetDiscountedPrice(double price, DateTime lastOfferTime)
{
     if (lastOfferTime > _dateTimeService.Now())
                return price * 0.25;

     return price;
}
            
        


Private methods

Private methods are only an implementation detail whose existence and behaviour are only justified by their use in public methods. In other words, once your public methods are tested, your private methods should be fully covered. 

The best way to test a private method is via another public method. If this cannot be done and you need to test private methods separately then try out the following alternatives.:

1. Change the private method into the public
2. Use reflection.
3. Moving out responsibility
4. Extend testing to "internal" classes/methods

I personally prefer the 4th one because it is clean, Microsoft also uses this in their asp.net core repository

https://github.com/dotnet/aspnetcore/blob/master/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs

        
[assembly: InternalsVisibleTo("Product.Services.Tests")]
namespace Product.Services
{
    public class ProductService : IProductService
    {
        // Internal for testing
        internal bool IsValidInput(string input)
        {
            //Check validitity and return
            return false;
        }
    }
}
            
        

In order to make the class more cleaner we can move the assembly block to our project file

        
<Project Sdk="Microsoft.NET.Sdk">
...
<_Parameter1>Product.Services.Tests</_Parameter1>
    </AssemblyAttribute>
  </ItemGroup>
...
</Project>
            
        


File System

Static methods are not good for testability. But sometimes we need to use File static methods. Since these methods are not part of our application or come from third-party libraries, we can't change the static methods. 

        
public async Task<IActionResult> GetProfilePicture(string path)
{
     await using (var file = FileSystem.OpenRead(path))
     {
          return File(file, ImageContentType);
     }    
}
            
        

To avoid the testability issue we can create a file service and access file through the service.

        
// Testable code
// 1. Create an interface
public interface IFileSystemService
{
     Stream OpenRead(string path);
     Stream OpenWrite(string path);
}

// 2. Inject IFileSystemService to constructor
private readonly IFileSystemService _fileSystemService;

public UsersController(IFileSystemService fileSystemService)
{
     _fileSystemService = fileSystemService;
}

// 3. Update your method
public async Task<IActionResult> GetProfilePicture(string path)
{
     await using (var file = _fileSystemService.OpenRead(path))
     {
         return File(file, ImageContentType);
     }
}
            
        


Database

To test the repository layer we need to mock the underlying EntityFrameworkCore layer. This means to mock EntityFrameworkCore classes, particularly entity context which is derived from DbContext and DbSet as a part of the DbContext.

Mocking database provider is difficult, cumbersome, and fragile. Luckily EF Core provides some solutions:

1: Production database system (LocalDB)
2: SQLite
3: The EF Core in-memory database

We recommend the EF in-memory database when unit testing something that uses DbContext. In this case, using the EF in-memory database is appropriate because the test is not dependent on database behaviour. Just don't do this to test actual database queries or updates.

But if the code is written this way, It will not be testable.

        
// Untestable Code

private readonly BlogDbContext db;

public BlogService()
{
    this.db = new BlogDbContext();
}
            
        

In this case, the new keyword should be removed as usual, but we don't need an interface for entity EF Core. We can directly mock the DbContext.

        
// Testable Code

private readonly BlogDbContext db;

public BlogService(BlogDbContext  database)
{
    this.db = database;
}
            
        


Third-Party Libraries
Third-party libraries most often become an obstacle while writing unit test. So we have to use them with testability in our mind. Some of the third-party methods may contain 'static' keyword. Though we can not remove the 'static' keyword, we can call the third-party static method in a non-static public method, extract an interface and use that method in the system under test through dependency injection. But what to do about third-party library like automapper?

        
// Untestable code

public async Task<IEnumerable<ArticleForUserListingServiceModel>> ByUser(string userId)
        {
            await this.db
                .Articles
                .Where(a => a.UserId == userId)
                .OrderByDescending(a => a.PublishedOn)
                .ProjectTo<ArticleForUserListingServiceModel>(Mapper.Configuration)
                .ToListAsync();
        }
            
        

Luckily, automapper has a built-in interface for Mapper, so we do not need to create new abstraction. We can simply inject the interface and make the code testable. 

        
// Testable code

private readonly BlogDbContext db;
private readonly IMapper mapper;

public ArticleService(
            BlogDbContext database,
            IMapper mapper)
        {
            this.db = database;
            this.mapper = mapper;
        }

public async Task<IEnumerable<ArticleForUserListingServiceModel>> ByUser(string userId)
        {
            await this.db
                .Articles
                .Where(a => a.UserId == userId)
                .OrderByDescending(a => a.PublishedOn)
                .ProjectTo<ArticleForUserListingServiceModel>(this.mapper.ConfigurationProvider)
                .ToListAsync();
        }
            
        

Following these practices is not just good for unit test but also improves the overall code design. It takes more time to write testable code but at the end of the day it really worth it.