C# has many toys when it comes to constructors. Classes can have many types of constructors;
- Default constructor
- Parameterized constructor
- Private constructor
- Static constructor
- Copy constructor
This topic is not about getting to know C# constructors but how to use them to write testable code. Now it is time to go check these constructor types if you don’t know these.
Basically a C# class has a default constructor generated by the compiler if you don’t provide one. You can have many parameterized constructors like function overloads and parameterless constructor in the same class. Copy constructors are used to pass the same type to another instance as a constructor parameter. Private constructors only exist in classes that you cannot create instances. Static constructors are the only interesting ones in the lot, they don’t take access modifiers, there can be only 1 static constructor to a class, they can’t have parameters, they can reside with default constructors.
I can hear you saying “I thought we were testing specs and functionality who cares about constructors?” You are right. When we do unit testing we are using a fundamental pattern called AAA;
In this step, we are setting up our test environment. Maybe creating some variables, some classes, if necessary some mock objects.
In this step, we are executing the test by calling a function most of the time.
In this step, we are checking if the output or the state changes that is caused by the Act satisfies our expectations.
So the Arrange step is directly relevant with constructors because we are creating instances. Passing constructor parameters. Acknowledging this and some simple rules can help you writing testable code. Even if you are planning to add tests later, your design and implementation should allow testing the behaviour easily. Here are the examples you should avoid;
Using “new” Keyword In Your Constructors
Lets start from the most common and easy to solve code smell;
We have a PathFinder class that finds texts / paths in a large xml. We decided to put the instantiation of the XMLReader into PathFinder. We want to test the Find logic.
As you see below we can test the Find logic perfectly. Everything is great.
Actually No! To test Find logic we instantiated a “new XMLReader()” in our PathFinder constructor. Everytime we try to test the Find logic we have to create this class instance too. Maybe today XMLReader doesn’t do much in it’s own constructor but somebody might change its behaviour and it may take longer. Even worse, it might be throwing an exception and that is another behaviour you should consider in your tests.
Also now you are stuck with XMLReader in PathFinder. You can’t load xml from another source or with an other implementation.
Unit testing implies testing of a Unit as the name implies. We are testing two units at this case.
ProTip: Unit tests should be so fast, and repeatable. You should be able to run them every time you change a bit of code. This way when you break something you can see what is failing and go fix it quickly rather than searching where you have failed in a long code or a large project. It’s all about isolating the functionality that is being tested. There are tools for every language running tests in background while you are coding and show you an interface.
How to make this code testable;
Polymorphism and Dependency Injection (DI) are your tools here. I know every unit test / tdd blog starts with them but be patient, I will show you more details in the following sections.
Created an IDocumentSource interface and changed the behaviour of XMLReader according to this. Now I don’t even need to have XMLReader class to test PathFinder.
Changed the PathFinder constructor. Now instead of creating an instance it accepts an injection as constructor parameter.
ProTip: You guessed it right. This is Dependency Injection (DI). It is not something complicated, we just pass documentSource as a parameter.
Also we use polymorphism; we use IDocumentSource interface as parameter instead of the concrete class. Now we can pass anything that satisfies that contract. We can pass mocks, stubs, fakes or the actual concrete class. Really helps us to isolate the functionality that we test.
Updated the test so that we return a dummy document using a Mock object instead of loading from large XML with the concrete XMLReader class.
Setup the mock, so that we are sure in PathFinder we call the LoadDocument and get the Document after it is loaded.
Now we have an extra line for actually reading document. We moved this behaviour to an upper level in callstack. This means our code is more flexible now. We can have one PathFinder and read many times.
Yes we have a little bit more code here but you will see the advantage. Note that this was for sake of providing an example. In real life the components are bigger and the amount of code is likely to be the same because the setup of the test is straightforward.
Now we have set the basics, let’s move forward with the next example.
Constructors Calling Static Methods
In your project you decided to create some static fields. When used for these reasons it makes testing hard..
- shared resources like in-memory caching
- utility classes
When you depend on these excessively, these classes become a burden when your project grows. It is ok to use Extension or Conversion methods as long as they don’t have side effects and are composed. You can test those individually. Other than that using static variables represent global state and it is really hard to test. That is a very common code-smell and you should be careful with those.
ProTip: For most of the hidden states in large classes, we extract those into other meaningful classes. This way we can test them separately also we can use those to have mocks/stubs to test the main classes functionality. It is a technique that we use a lot when we refactor to more testable code.
Let’s say we have a Counter class and another Class1 calling this Counter method in its constructor.
It looks simple. Every Class1 that we create gets a new Index number starting with 1.
Lets try to unit test this behaviour.
Yes, as you have guessed we got errors.
Remember these TDD / unit test rules
- Tests should be fast
- Tests should be repeatable at any time
- Tests shouldn’t have state so that we can call them parallel (this is also true for test fixtures)
- Tests should not depend on each other to run
- Tests should be as simple as possible, preferably asserting one thing
In this example our tests can’t run in parallel and they are not repeatable. Should we implement a Reset method in our Counter class just to test it? What if in the future we run our tests on a fast Bamboo server in parallel with 50 more tests addressing this Counter behaviour? Between Reset and Increment methods you can have parallel tests Increments. Then you have wobbly test results. So the answer is No!
What is the testable approach here?
In our Class1 specific unit tests, we are testing the behaviour of Class1 only. Knowing what to test and what is not in the test scope is really important. In our example, calling Increment and checking the Index is 1 but the Index is not important for us. Currently we are doing these tests for just code coverage. The main target should be checking if the Increment method of the Counter called is in our constructor. In the future when somebody deletes this code accidently, our test should fail.
If we change the Static class to non-static, now we can inject a copy of Counter class into Class1. Yes, I break things by converting a static class to non-static. But other classes were dependent to the implementation of Counter. Now they don’t care if the injected reference is defined static somewhere upper tier in architecture. You can now decide how to instantiate Counter. Maybe when your project gets larger, you decide to have an IoC container. C# has abundant list of IoC containers. Then get rid of static copy of Counter and have it as a singleton. You have many options. Do you recognize how writing testable code changed your architecture in a better way?
Now we can mock the injected ICounter and test if it’s called in the constructor.
We did it! We could use any number instead of 1, 2 and assert it because we are independent of Counter implementation. Remember we are testing the unit behaviour of Class1 here. If somebody decides to change Class1’s constructor in the future, these tests will assert the usage is still there.
Pro Tip: We have extended the testing strategy here. Sometimes we don’t even need to use assert directly in our test AAA structures. You don’t have to. When you get used to unit testing, you will see that AAA structure blends into SetUps, TearDowns, explicit Mock behaviours. You can see the MockBehavior.Strict and VerifyAll() in the TearDown. That is already an assertion.
Now our tests pass.
Constructors Having Business Logic
I have read somewhere “Do not put any business logic or initialization code into constructors, only set parameters”. That looks very strict right? Lets elaborate on this a little bit.
There is no “right” way to do things in C#. There are many debates of constructors having logic. You can write business decisions to your constructors. Some of the publicly recognized C# patterns are executing decisions in their constructors. But even if they are proven to be good, that doesn’t mean they are testable.
Also initialization may still have logic. Maybe in your constructor you try to connect to a database. At first this design seems error free but that is an external domain to your application scope and it may throw errors. You would like to catch those and maybe throw something else, or handle gracefully or retry connecting. But testing these are also hard if they are in constructors.
ProTip: I simply recognized recently, constructors are not named code blocks. I mean you can’t name them properly. A constructor doing xml file loading should be renamed as “LoadXmlFile” or a constructor checking database connection should be named as CheckDbConnection right? That looks like a function then. Remember most of our work is naming things.
In this example we have a UserRepository with a constructor accepting cacheEnabled parameter as Boolean.
There is a business logic in constructor. If the cache is enabled, it decides to get all users from database into cache. If we connect this class to db and actually get users instead of returning null, it will work without any problems.
Lets plan some basic unit tests;
- Test1 – If cache enabled, all users should be loaded into cache
- Test2 – If cache disabled, there shouldn’t be a database call in constructor
- Test3 – Get single user from cache
- Test4 – Get single user from database
At this moment we can come up with more tests about this class’ behaviour. I tried to write two of the tests above with no avail;
At first test, there is no way to assert if GetAllUsersFromDb() is called or not. (Unless we add a public Boolean state as a backdoor and set it to true when it’s called or try to use reflection to check if this function is called)
At the second test, there is no way without connecting to db and getting all the users into cache. Remember what we said about tests being fast and repeatable. This test might take many seconds and also in a developer machine maybe it is impossible to cache because of large database.
With this approach if we had more constructor parameters, we wouldn’t know about the method calls and combinations inside the constructor. Even if we had the states for all the behaviour it is really hard to be sure. This code doesn’t look like testable and prone to future errors.
To test a closed system behaviour we need a defined cause and a measurable effect.
Let’s say we had a TV. We pressed the button on remote and TV is on now. In between these two observable things, IR message sent from remote, TV received it, then many parts in TV started working (power supply, tuner, logic board, lcd etc.) as a result we got the picture and sound. We don’t know what magic they are doing at the background. But we have a cause and an effect.
If we are testing the integration, we test all these aspects acting together end to end. For unit testing we need to isolate every unit and test them separately.
In our example, what happens the constructor throws an error? Or maybe we want to change the cache mechanism or database connectivity. How do we change these without breaking UserRepository unit business?
Here are some architectural problems;
- We are violating SRP – Single Responsibility Principle. UserRepository shouldn’t run as a caching engine. This is not the business ruleset of UserRepository.
- Cache mechanism is bound to Repository, there is no way to change without changing Repository.
- There should be a way of separating database engine, cache engine, UserRepository wrapper and actual data.
- We need isolation of all these separate responsibilities so that we can test them individually.
ProTip: Single Responsibility Principle is the “S” of the S.O.L.I.D principles. It means every module or class should have responsibility over single part of the functionality. As Robert C. Martin explains with reasoning: “A class should have only one reason to change”. All of the SOLID principles should be applied to our daily programming and testing practices.
As I stated at the beginning current implementation may run without problems. But even if this doesn’t look like a problem now, when your project gets larger, refactoring and changing the code that is not tested is risky. Unnecessary dependencies may block your future maintenance or increase your effort.
Here is how to make it testable;
A lot of change required for those architectural problems. Lets see what we have done;
- Now database engine and cache engine are different classes. We don’t test them here so we don’t need to decide them now. We only need to focus on UserRepository behaviour.
- UserRepository is merely a wrapper over database engine and cache engine. It’s code is now more simple. Remember to “Keep it simple, stupid”
- No constructor code required. Cache engine data should be handled outside UserRepository domain. UserRepository is no responsible to fill the cache data.
- We only set parameters in the constructor
- Now our code is testable
Check the final UserRepository code here. It looks more complete. No gray areas, no comments, no todos, just simple. We don’t have to try to implement and decide large behaviour here so the changes we did drive us to a finished, working, testable code.
Protip: A factory pattern might be used here. A factory is a way to create the object without exposing the creation logic. It’s responsibility is to compose the object which supports Single Responsibility.
Here are some tests;
The details you should check here are;
- We set the mock objects strictly. SetUp method runs before every test.
- TearDown method runs after every test so we verify the mock behaviour. If any mock setup is not satisfied we get a warning.
- Now it is easy to test that database not calling GetAllUsers on creation of UserRepository.
- Also setting up mocks for database engine and cache engine is easy. For example; If cacheEngine.GetData method is not called during execution we get a mock exception and test fails.
- AAA structure is blended into refactored test code. When we are Asserting we are also Acting (Assert … repo.GetUser)
- There is no Assert line at the first test. Instead we verify GetAllUsers never called inside new UserRepository.
In this chapter we had a sneak peek of how to use constructors while writing testable code with C#.
In short, we should be careful with these code-smells;
- Using “new” keyword in constructors
- Calling static method in constructors
- Setting hidden state in constructors (either static or not)
- Logic code in constructors (also loops should be carefully investigated as code-smell)
What we can do about it;
- Single Responsibility Principle rules should be checked. The code-smell mostly is at constructors
- Separate and isolate those large classes so you have simpler, testable constructors
- Think about if the class is a data class or a logic class. Separating those concerns will let you have clean constructors (and better design)
- Move the hidden state to external classes
- Dependency Injection is your friend
- Keep It Simple Stupid (KISS)
These are general guidelines to follow at the beginning of your unit testing adventures. Of course, there are asides and exceptions. Remember there is not a single way to do things in C#.
You can download all the example code from this link.
Happy Coding 😊
I hereby thank my friend and colleauge Bas van der Linden for reviewing and making suggestions.