Hone your craft: approaches to making code testable

I recently read a blog post about using shims to unit test without interfaces. It looks really compelling, so I thought I’d explore some ways to create testable code to determine if I like the idea of shims or not.

In this blog post, I’ll explore a couple of ways to turn a simple, yet complicated piece of c# code into something testable. While this code is written in c#, I would assume the principles apply universally to most object oriented langauges.

The task

Let’s begin with the beginning. Imagine you have to write code, that can determine if a date was yesterday.

The solution

This is the first version:

Testing the solution

So, now I have a program, that takes a date as input and determines if the date was yesterday. This is easy to test manually. So let’s do that:

Great, the code works. But it would be better to write some automated tests rather than having to run all imaginable tests by hand.

Automating tests

Scripting

This could of course be done by writing some scripts, but we’ll skip this option and write some unit tests instead.

Unit testing

The first thing to do is to determine what to test. In this case the tricky thing is to determine if the entered date is yesterday, so this will be the focus of this test.

The code that prompts the user for input or the part that converts the input to a c# Date object is less interesting in this context.

To achieve testable greatness we first have to move the test-worthy code into a separate method/class, so the unit tests don’t depend on the input/output device being used:

Now it is possible to introduce some unit tests:

An error

These unit tests will reveal a problem with the code. It doesn’t determine if the date was yesterday, rather it determines if the date and time was somewhere between 24 and 48 hours ago.

The solution

So a fix is in order:

Amazing, our automated test has already payed off.

A problem – tests will fail tomorrow

But there is a problem. For those tests to work, the computer running them has to have the date set to 2018-03-21. Otherwise the call to DateTime.Today inside WasYesterDay is going to return a date, that makes the tests fail.

Solutions

There are several ways to view this problem and several ways to solve it.

  • Don’t hardcode dates in unittests
  • Use shims to overwrite DateTime.Today
  • Make the code pure
  • Interfaces and DI
  • Introduce a DI framework
  • Use mocks

Let’s go through them one by one.

Don’t hardcode the dates in the testcode

You could argue that the code works like it should and that the problem is that the testcases provide a hardcoded date, while the method we are testing is using the current date.

We can solve this problem by not hardcoding the date in the testcode:

This will work.

But I would prefer my testcases to not use data generated on the fly. While I don’t immediately see the problem here, it is just good practice to ensure that the unit tests are predictable and reproducable, and these are not, because everytime they are run, they get run with new input data.

Use shims to overwrite the call do DateTime.Today

Another way to solve the problem is to “hard-code” what date the WasYesterDay gets from DateTime.Today. For this purpose we’ll use Pose to create a shim, that intercepts the call to DateTime.Today and replaces it with a fixed value, when the test is run:

With this approach we managed to make the tests predictable and reproducable not changing the code in question. Unfortunately, the current incarnation of Pose.Shims makes it impossible to set breakpoints in the isolated code and Visual Studios Live Unit Tests get confused by it and reports false positives.

However, this solution works from the assumption, that the problem was with our testcode.

You could argue that the real cause of the problem was, that the DateService was not designed to be testable.

Make the code under test a pure function

The simplest way to solve the problem is to change the WasYesterDay method to be pure, meaning that the output is ONLY a result of the input with no dependencies to state (be it object state or the state of the universe). This approach is (for me at least) inspired by Mark Seemanns talks and blogposts about pure and functional programming e.g. the the From dependency injection to dependency rejection series.

In this case we have to remove the dependency on the universal space-time continuum introduced by the call to System.DateTime. Instead the caller to WasYesterDay has to provide both the current date and the date in question.

Now that the code is pure, it is much easier to write tests and the total code looks cleaner. It is also easy to test edge cases:

The drawback however, is that the caller now has to provide two arguments to the functions instead of just one.

Create an interface and use dependency injection

A more traditional approach in object oriented programming is to isolate the problematic dependency behind an interface and dependency inject different implementations of that interface depending on wether we are testing code or running it “for real”.

First the interface:

The DateService then needs to depend on IClockService, which means that WasYesterDay can no longer be a static method.

We use the interface in the application:

Then we create an alternative implementation for the tests:

And use it in the unit tests:

So, there we have it, a solution that uses dependency injection.

Use a dependency injection framework

Using dependency injection usually also involves using a framework to resolve dependencies. Otherwise every piece of code calling DateService would have to manually resolve the dependency to IClockService.

Microsoft.Extensions.DependencyInjection to the rescue:

And of course the test code should not be considered a 2nd class Citizen:

However, we might want more granular control in our unittests. Maybe we want to ensure that DateService calls the implementation of IClockService exactly once?

Use Mocks

Instead of writing our own implementation of IClockService we can use a mocking framework such as Moq to create a mock that implements IClockService and provides us more testing abilities.

This saves us the trouble of creating an implementation of IClockService and makes it easier to control the behavior of the IClockService implementation.

We can easily specify in a testcase that the property get’er should emulate edge-cases such as throwing an exception or return DateTime.MaxValue.

For instance we could check if the code still works as we approach the end of days:

In the example above it also gave us the ability to ensure that the implementation of IClockService.Now gets called exactly once. I would argue, however, that this is not necessarily a good thing. I’ve seen examples of unit tests that use all the possible features of Mocks to ensure nothing can change without some tests breaking. This effectively makes it impossible to refactor anything.

So while the extended testing abilities of mocks can be great they should be used with extreme caution, otherwise the unit tests can end up doing more damage than good.

Conclusion

I started writing the blog post, to explore the shims approach. On the face of it, it looks really cool. But after exploring and considering the options, I’m left with the impression that using shims for unit tests is the symptom of a problem (poorly designed code), not a solution to it.

By far the most normal solution is using interfaces, dependency injection and mocks.

Personally I’m inclined to prefer the pure function approach because the code becomes cleaner, simpler and smaller.

But what do you think? Let me know in the comments section below.

3 Comments

  1. Tomas Lycken

    > But after exploring and considering the options, I’m left with the impression that using shims for unit tests is the symptom of a problem (poorly designed code), not a solution to it.

    I tend to agree here – however, shims have an important place when trying to move an existing code base away from a poor design. It’s unfortunate but unavoidable that many of the code bases that are hardest to test because of problems like using DateTime.Now, are also the ones that are hardest to refactor – but it’s much less scary to refactor if you can specify the expected behavior in a couple of tests first.

    A shimming library like Pose is not something I’d use for testing in a greenfield project, but it’s a nice tool to have access to in brownfield situations, to make refactoring into a more testable design feel less intimidating.

  2. Thank you for share very nice knowledge. Your website is so coolI am impressed by the information that you have on this blog. It shows how well you understand this subject. Bookmarked this page, will come back for more. I found just the information I already searched everywhere and just couldn’t find. I like this site shown and it has given me some sort of desire to succeed for some reason, so keep up the good work

Trackbacks for this post

  1. Szumma #102 – 2018 12. hét | d/fuel

Leave a Reply