X

This site uses cookies and by using the site you are consenting to this. We utilize cookies to optimize our brand’s web presence and website experience. To learn more about cookies, click here to read our privacy statement.

Making Unit Testing Great Again. Part 1

What makes unit testing hard? For me it is the setup of external dependencies. Unit testing is easy when you have a method that calculates the Fibonacci sequence,  Lloyd algorithm, or calculates the sum of two numbers. Some of these cases are complex, but that complexity is so-called Intrinsic complexity. Code that only has intrinsic complexity is easy to unit test. You simply have a method, give that method an input, and expect something on the other side. It does not really matter how complex the code inside is. But it becomes a whole different animal when you throw in external dependencies. External dependencies need setup, and setup adds extrinsic complexity. Extrinsic complexity is the kind of complexity added to a problem in order to help us solve it. For example breaking a method into multiple methods adds extrinsic complexity, breaking a class into multiple classes adds extrinsic complexity. Extrinsic complexity makes our code more maintainable but harder to test.

External dependencies are the enemy of unit tests

Let’s take a step back and see how we can verify that our code works as expected. The first and most obvious way is to just review our code manually (visually). The upside of this method is that it is relatively cheap. We don’t need to write new code and we don’t need to maintain that code.  This method works really well with simple code that is easy to follow and hold in your brain. The downsides of this method is that it does not scale well and it becomes increasingly difficult to do as the complexity of the code grows.

Automated tests are another way to test for the correctness of our code. The upside of automated tests is that they are easily repeatable, and they are fast. The downside is they require writing code and code is a liability. Code can have bugs. How do we know that our test code is bug free? Do we need to write tests to test the tests? Of course not. We depend on manually reviewing the tests and ensuring they are bug free. To be easily reviewable test code must be simple. But how do we keep our test code simple? The only way to keep the test code simple is to keep the code they are testing simple. I mentioned “simple code” a few times already so let me define what I mean by simple.  Implementing the Lloyd’s Algorithm is by no means simple in the common sense of the word, but from the perspective of unit testing, it is quite simple. We have one place to insert the input and one place where we expect the output. There are no external dependencies. External dependencies are the unit test enemy.

Why are external dependencies bad for our unit tests?

External dependencies add complexity, and it is the bad kind of complexity. Unit testing code that has external dependencies requires us to use mocks and mocks need to be setup. Setup adds extra code that needs to be maintained. Another “feature” of mocks is: they tie our tests to the internal implementation of the object under test. And what we really want to be tied to, is the end result of the operation. How many of you have thought “Man I really don’t want to go in that service and change anything because, I don’t want to spend the rest of the day fixing broken tests”? I have. And that is a huge, huge red flag. One of the main benefits of unit testing is supposed to be helping you refactor code, and if you are having that thought, something must be wrong. I have heard people blame the mocks, but the problem is not the mocks the problem is the way we write our code and tests. Mocks are just a tool that facilitates that way of coding and testing.

But if we have been writing unit tests wrong, what is the right way to write them?

The answer is….  DON’T WRITE THEM! At this point some people will probably stop reading, and say “this guy does not know what he is talking about, we need our unit tests!” But wait, let me explain. What I mean by “DON’T WRITE THEM!” is: don’t write unit tests for objects/methods that don’t need to be UNIT tested. Not saying don’t write tests, just don’t write UNIT tests. You surely want to at least have some smokescreen tests covering all of your code, but those need to be higher level end to end tests and not unit tests.

Enter “Orchestators” and “Decision makers”

So what does an object that we should not try to unit test look like, you might ask. For the purpose of this discussion I will divide objects and their public interface methods in two categories. “Orchestrators” and “Decision makers”. The public interface of “Orchestrator” objects is only comprised of “orchestrator” methods, and the public interface of “decision maker” objects is composed of “decision maker” methods. “Orchestrators” are the objects that don’t need to be unit tested and “decision makers” are the objects that must be unit tested. Each one of them has its particular role in our program, and if you want to keep mocks out,

Do NOT mix “Orchestrators” and “Decision makers” together!!

Here is what an “orchestrator” object looks like:

[code language=”csharp”]
public class OrchestratorObject{

private readonly _objectBRepo;
private readonly _objectCRepo;

pbilic OrchestratorObject( ObjectCRepo objectCRepo, ObjectBRepo objectBRepo){
_objectBRepo = objectBRepo;
_objcetCRepo = objectCRepo;
}

public ObjectA OrchestratorMethodExample(Guid objectCReference, Guid objectBReference)
{
var objectA = new ObjectAClass();
var objectC = _objectCRepo.GetObjectC(objectCReference) ?? new ObjectC();
var objectB = _objectBRepo.GetObjectB(objectBReference) ?? new ObjectB();
var someValue = _serviceX.CalculateSomeValue(objectC, objectB );
objectA.SomeProperty = someValue;

return objectA;
}
}
[/code]

I call it “orchestrator” because its only purpose in life is to orchestrate calls to other objects. “Orchestrator” objects’ public interface is composed of only “Orchestrator” methods and those methods have only one path of execution. If all of the calls to different services succeed, there is no way that “Orchestrator” methods will not complete successfully, and return the correct result. And that is really the key.  If we know that a method WILL succeed, why test it?

But what if one of these repo calls does not complete successfully? Well that is not a responsibility of the “Orchestrator” or its tests. Each of those repo methods will have its own unit tests, that will cover the different scenarios. And if an exception happens within them, it is their responsibility to handle it. Any handled exception scenarios will have to be addressed at the program entry point.

Not only that, the unit tests of “Orchestrators” are not needed, but they can also create maintenance nightmares and can turn a routine code change into an ordeal. For example, let’s say we change our mind and instead of making the calculation in OrchestratorMethodExamlple, we now want to make the calls to the repositories inside of the serviceX. Instead of passing objectC and objectB to _serviceX.CalculateSomeValue we have serviceX.SetSomeValue method, and we do the calculation of SomeValue in it. The code in our “Orchestartor” will now look like this:

[code language=”csharp”]
public ObjectA OrchestratorMethodExample(Guid objectCReference, Guid objectBReference)
{
var objectA = new ObjectAClass();
_serviceX.ReferenceC = obectCReference;
_serviceX.ReferenceB = obectBReference;
_serviceX.SetSomeValue(objectA )

return objectA;
}
[/code]

We changed the implantation of our method, but the end result is expected to be the same. If we had unit tests covering OrchestratorMethodExample, we would have to go in them and remove all expectations for calling _objectCRepo and _objectBRepo, we would have to hunt down all the setup related to those two repos, and remove it. If we did not write the unit tests on the other hand, all we need to do is delete the 2 lines of code, that are making the extra calls, and call the new method SetSomeValue. Done! No broken unit tests to worry about, and our smokescreen tests still pass as before.

The responsibility of an “orchestrator” is to make sure that the “decision makers” are given all their required data, and orchestrate the calls to those decision makers.

But what is a “decision maker” object then? The only purpose of a decision maker is to calculate stuff and make decisions based on certain rules. The key characteristic of “decision makers” is that every piece of data they might need should be given to them in a ready to use form. They should not ask for things. The point is that decision makers, once created, should not depend on the outside world to do their job.

In summary

To make unit testing easy and avoid having to handle external dependencies in your unit tests, try to break your code into “orchestrators” and “decision makers”. Unit test the hell out of your “decision makers”, and depend on the results of your integration tests to determine if “orchestrators” are calling the right “decision makers”, and calling them in the correct order. Forget about mocks.

In my next post I will walk through refactoring of an orchestrator that also makes decisions, and get rid of the unit test’s biggest enemy – External dependencies.