Click here to Skip to main content
15,886,258 members
Articles / DevOps / Unit Testing

Unit Testing - Abstracting Creation of Simple Values

Rate me:
Please Sign up or sign in to vote.
4.75/5 (3 votes)
8 Nov 2018CPOL6 min read 5.8K   1  
A discussion of three options for the creation of simple .NET types without affecting the testability of your class

Originally posted here.

Introduction

My occupational coding has recently led me down a path of writing a huge number of unit tests. The team I work in are attempting to implement a test-driven development (TDD) methodology, and having previously worked in development teams that did not do any automated testing (I know...GASP!!!), it has presented me with some interesting challenges.

Of course, I've not come up against anything new. Many developers have come across all of the same problems I have come across. I'm grateful to those that have put their solutions to those problems on the internet so that in my times of desperation, I've been able to dig myself out of a code hole. It's with this same spirit that I offer this article.

Defining the Problem

I've found it to be a common occurrence when building classes that I need to implement some kind of very basic system logic, something that is often based on static methods or properties of the core .NET library. Probably the most common example is wanting the current date time for a record you might be about to save. If you've ventured 5 minutes beyond the length of time it takes to complete a "Hello World!" tutorial, then you'll likely know that this is absurdly simple to accomplish using DateTime.Now. Take the following example:

C#
public class DocumentService : IDocumentService
{
    private IRepository _repository;

    public DocumentService(IRepository repository)
    {
        _repository = repository;
    }

    public void CreateNewDocument(string documentName)
    {
        var document = new Document
        {
            Name = documentName,
            Created = DateTime.Now
        };
        
        _repository.Save(document);
    }
}

If you just want to get an application up and running (or if you work for my previous employer), then this is fine. The CreateNewDocument method in this service class only does two things:

  1. Creates a document with the name and current timestamp
  2. Saves the document

However, if you wish to add unit tests for the DocumentService class, then you'll quickly realise that a "hard dependency" now exists on the .NET DateTime class that you are stuck with. How do you go about testing that the Document.Created was set correctly?

Here, I will share a few options. Each one solves the problem of testing the DocumentService class but with their own advantages and disadvantages. In my opinion, each option will be an improvement over the one that precedes it.

Option 1: Provide the Value as a Method Argument

One way you could avoid this dependency is to pass in the current timestamp as a parameter of the method - see the example below:

C#
public class DocumentService : IDocumentService
{
	private IRepository _repository;

	public DocumentService(IRepository repository)
	{
		_repository = repository;
	}

    public void CreateNewDocument(string documentName, DateTime currentTimestamp)
	{
		var document = new Document
		{
			Name = documentName,
			Created = currentTimestamp
		};
		
		_repository.Save(document);
	}
}

Whilst this solution will make the DocumentService class testable, it hasn't really solved the problem. The responsibility for solving the problem has just been delegated to someone/something else.

Any class which calls the CreateNewDocument method will now have to implement a way of making a DateTime object to represent the current timestamp and pass it along. Most likely, this will result in another untestable usage of DateTime.Now higher up in the call stack.

This isn't ideal because now the calling class not only has more work to do, but also must know how the DocumentService intends to use the date time value so it can provide the right one. This knowledge of the internals of the DocumentService should not be required.

Option 2: Inject a Service to Provide the Value

A common pattern to solve this problem is to create a wrapper around the functionality you cannot test so it can be replaced when creating unit tests.

C#
public interface IDateTimeProvider
{
    DateTime Now { get; }
}

public class DateTimeProvider : IDateTimeProvider
{
    public DateTime Now => DateTime.Now;
}

This is often referred to as the "facade pattern", where the underlying complexity of an operation is hidden behind a simple interface - even though there isn't much actual "complexity" here.

The DateTimeProvider can now be injected into the document service via the constructor.

C#
public class DocumentService : IDocumentService
{
    private IRepository _repository;
    private IDateTimeProvider _dateTimeProvider;

    public DocumentService(IRepository repository, IDateTimeProvider dateTimeProvider)
    {
        _repository = repository;
        _dateTimeProvider = dateTimeProvider;
    }

    public void CreateNewDocument(string documentName)
    {
        var document = new Document
        {
            Name = documentName,
            Created = _dateTimeProvider.Now
        };
		
        _repository.Save(document);
    }
}

When writing your unit tests, you can use your chosen mocking framework to provide a fake implementation of IDateTimeProvider that provides a static value for _dateTimeProvider.Now.

This is a very practical pattern for many situations, especially if the requirements of the provider become more complex. However, there are some notable disadvantages to this approach.

Firstly, the actual requirements here are very simple, so it could be considered overkill to create an extra class for the provision of a single date time object. Especially if you consider that you'll also need to configure dependency resolution if you're using an IOC container and instantiate mock objects in your tests. Maybe creating a provider object is more effort than it's worth.

Secondly, as noted with method injection, it is reasonable to suggest that the responsibility of choosing and applying a timestamp should be with the DocumentService itself. Any consumers of this service class will be required to provide an appropriate IDateTimeProvider instance, which seems to break the flow of responsibility.

Option 3: Use an Injectable Function Property

I've not found any other references to this pattern, so I've elected to name it an "Injectable Function Property" - if anyone knows of a better/more appropriate name for this, then please let me know.

C#
public class DocumentService : IDocumentService
{
    private IRepository _repository;
	
    internal Func<DateTime> GetCurrentTimestamp { get; set; } = () => DateTime.Now;

    public DocumentService(IRepository repository)
    {
        _repository = repository;
    }

    public void CreateNewDocument(string documentName)
    {
        var document = new Document
        {
            Name = documentName,
            Created = GetCurrentTimestamp()
        };
		
        _repository.Save(document);
    }
}

This solution has several advantages over method injection and provider injection:

  1. The default behaviour is contained to the class that requires it.
  2. No extra classes are required.
  3. No additional IOC configuration is required.
  4. Mocking the behaviour is much simpler (see code example below).
  5. The GetCurrentTimestamp property only exists on the class, so consumers of the interface will not have to access the property.
  6. The GetCurrentTimestamp property is internal, which prevents classes outside of the current assembly from updating it.

Here is an example unit test for the document service class:

C#
[TestClass]
public class DocumentServiceTests
{
    [TestMethod]
    public void CreateNewDocument_SavesDocument()
    {
        var testTimestamp = new DateTime(2018, 1, 1);
        Document savedDocument;
		
        var mockRepository = new Mock();
        var documentService = new DocumentService(mockRepository.Object);

        mockRepository
            .Setup(x => x.Save(It.IsAny()))
            .Callback(x => savedDocument = x);

        documentService.GetCurrentTimestamp = () => testTimestamp;
		
        var documentName = "TEST DOCUMENT";
		
        documentService.CreateNewDocument(documentName);
		
        Assert.AreEqual(documentName, savedDocument.Name);
        Assert.AreEqual(testTimestamp, savedDocument.Created);    
    }
}

I used DateTime.Now for my example of this pattern because it's the most common reason I've used it, but I have used it for several other things. Examples include getting new GUIDs via Guid.NewGuid(), or obtaining a new TextWriter implementation.

C#
public class WriterService : IWriterService
{
    internal Func<TestWriter> GetTextWriter { get; set; } = () => new StringWriter();

    public WriterService()
    {
    }

    public void DoSomethingWithADocument(Document document)
    {
        using (var textWriter = GetTextWriter())
        {
            WriteDocumentToString(textWriter, document);
        }
    }
}

By adding this GetTextWriter property to inject a function, I was able to provide a mock implementation of a text writer in the unit tests which allowed me to verify that it was being called correctly and also being disposed of correctly (which was its own challenge that I'll save for another day).

There are two notable drawbacks to this pattern.

Firstly, there is nothing to prompt you to provide a new function when writing your tests, so it would be easy to forget and leave your tests using the DateTime.Now default.

Secondly, other developers in your team are not likely to be familiar with this pattern. It is likely that you will be questioned about it when you try to sneak it into a code review or when someone else comes to work on the same class. You could always add a comment in the code linking to this article and give me some more traffic (although as a general rule, I think comments in code are bad).

Final Words

I hope you find this pattern useful. It seems to me to be the simplest and cleanest way of taking care of these simple concerns when using .NET without leaking responsibilities to something outside of the class. If you have a better way, then please let me know. I'm always happy to learn new ways of doing things.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --