Software testing is a very important part of any good software development process. As developers, we should test our software to gain the confidence that once deployed in production, the software is going to work as expected.
This article is published from the DNC Magazine for Developers and Architects. Download this magazine from here [PDF] or Subscribe to this magazine for FREE and download all previous and current editions.
Automated tests
An important type of software testing is Automated Testing. As opposed to manually running the software to verify its behavior, automated testing is about writing special testing code that runs some part (called the System Under Test (SUT)) of the software, collects results, and compares such results with predefined expected results. This helps reduce the amount of time needed to test the software, as testing needs to be done periodically. It also gives us more confidence that modifying the software (to refactor or change behavior) won’t introduce regressions.
We can further classify automated testing into two types: unit testing and integration testing. While the community does not completely agree on the difference between these two, a good definition can be found in the second edition of The Art of Unit Testing book by Roy Osherove.
Basically, a unit test tests a single unit of work in isolation. This unit of work can span a single method, a single class, or multiple classes. Moreover, we cannot use databases, web services, the file system, etc. in unit tests. Unit tests also need to be deterministic and have to run fast.
To be able to test a single unit in isolation, we need to replace any dependencies that it might have with fake objects (more on this in the next section).
An Integration test, on the other hand, can include databases, web services etc., and therefore is allowed to be slower than unit tests.
There is more to these definitions and to the difference between these two types of tests. You can find more information at the web site of the book here: http://artofunittesting.com/definition-of-a-unit-test/
Fakes
Fakes are objects with fake behavior that we can use to substitute real dependencies in order to isolate the unit under test.
For example, consider a class that does some financial calculations, say XYZFinancialCalculator, and that has a dependency on a currency exchange rate provider dependency. Such dependency would be injected into this object via an interface called something like ICurrencyExchangeRateProvider. In production code, there would be an implementation of this interface called WebServiceBasedCurrencyExchangeRateProvider that would communicate with a commercial currency exchange rate web service to obtain exchange rates. If we want to test XYZFinancialCalculator, then we wouldn’t want to use the real exchange rate provider because of the following reasons:
- The values returned by the real service are unpredictable which makes the behavior of the SUT itself unpredictable.
- It is slow to speak to a real web service which would increase the test execution time.
- It might cost money to communicate with a commercial service.
- We could get a false positive if there is a problem with the commercial service itself.
Instead, we can create a fake object that implements the ICurrencyExchangeRateProvider interface and inject it into an instance of the XYZFinancialCalculator class. This fake object would be predefined with a list of currency exchange rates.
Fake objects can be created manually or they can be created using an isolation framework. Creating a fake manually means that we create a class that implements the interface we want to fake, and that we write code in this class that represents the fake behavior. Here is how a manually created fake of ICurrencyExchangeRateProvider might look like:
public class FakeCurrencyExchangeRateProvider : ICurrencyExchangeRateProvider
{
private double usdToEuroRate;
public FakeCurrencyExchangeRateProvider(double usdToEuroRate)
{
this.usdToEuroRate = usdToEuroRate;
}
public double? GetExchangeRate(Currency from, Currency to)
{
if(from == Currency.USD && to == Currency.EURO)
return usdToEuroRate;
return null;
}
}
We can create an instance of this class and provide a value of USD to EURO exchange rate that we want this fake object to return:
var fakeExchangeRateService = new FakeCurrencyExchangeRateProvider(0.94);
With isolation frameworks, we can create a fake object without defining a new class. Here is an example that uses FakeItEasy to create an equivalent fake object:
var fakeExchangeRateService = A.Fake< ICurrencyExchangeRateProvider >();
A.CallTo(() => fakeExchangeRateService.GetExchangeRate(Currency.USD, Currency.EURO)).Returns(0.94);
Two types of fakes
We can classify fakes into two types: stubs and mocks. Again, there does not seem to be a complete agreement in the community on the difference between these two. So, I am going to rely mainly on the definition from The Art of Unit Testing book I mentioned earlier.
Basically, a stub is used to replace a dependency of the unit under test, so that the test can pass without problems. When we use stubs, we make no assertions (in the Assert phase of the test) that the stub was invoked, or not invoked, in any way. We can however configure the stub to act in a particular way. For example, we can create a stub of ICurrencyExchangeRateProvider that returns a rate of 0.94 when it is asked for the exchange rate from US dollars to Euros. However, in the Assert phase of the test, we don’t check whether the SUT communicated with the stub.
Mocks also act like stubs. They substitute the dependencies of the SUT. However, in the Assert phase of the test, we verify that mocks were invoked in a certain way.
One way to think about stubs and mocks is to think that stubs are used to substitute dependencies that represent queries; and mocks are used to substitute dependencies that represent commands.
If we have a dependency that we use to obtain/query data (e.g. exchange rates, or customer information, etc.), we should only care about replacing this dependency so that we control which data it provides - so we use a stub.
On the other hand, if we have a dependency that is used to create some side effect (write customer information to the database, writing a message to the queue, etc.), we probably need to verify that such a side effect has actually taken place - so we use a mock.
Levels of testing
We can write our unit and integration tests on different levels. For example, the SUT of a unit test can be a single object, an object graph of any size that represents some component in the system, or the whole application (without the real dependencies).
When we do integration testing, we can also test a single class with an external dependency (e.g. database) or a component consisting of an object graph of any size that also has external dependencies, or even the whole application (this is sometimes called end-to-end testing).
Based on this, a test has the highest level, if it has the whole application as the SUT; and the lowest level, if it tests a single method.
Lower level tests are usually easier to create, and they run faster than higher level tests. However, higher level tests are less likely to break when we refactor the internal structure of our application.
The SOLID principles
The SOLID principles are a set of five principles that aim at creating software that is flexible and maintainable. When we apply these principles, our code base becomes a set of small classes that each has a single responsibility. These small classes are highly composable which makes it easy for us to compose them together to achieve the complex behavior that we want for the application.
Classes become composable by having their dependencies injected into them instead of them creating these dependencies, and by having dependencies on abstract types or interfaces, instead of concrete classes. This is what Dependency Injection (DI) is all about.
For an example on how to compose objects, please refer to the Object Composition with SOLID article.
The Composition Root
The Composition Root is the piece of code in an application that creates instances of the application classes, and connects them to each other to make the object graph that formulates the application.
In the article, Clean Composition Roots with Pure DI, I described how we can use Pure DI to create Composition Roots that are maintainable and navigable. Pure DI is simply the application of Dependency Injection without the use of a DI container. If you haven’t read that article, please go ahead and read it because this article builds on the ideas explained in that article.
Here are some properties of a clean Composition Root:
1. The Composition Root is partitioned into many “Create” methods, each responsible for creating some component of the application.
2. In order to create a component, a method can create new class instances, call other methods that create subcomponents, and then wire all of these together.
3. The Composition Root is navigable. In order to understand the application, a programmer can go to the Composition Root, and in the first method, know what the highest level components of the application are. This method will include method calls to create these components. The programmer can navigate to any of these methods to understand the subcomponents of the corresponding component. From there, the programmer can do the same thing to understand lower-level components. This navigation process can continue until the programmer gets to the lowest-level components.
4. The Composition Root is concrete. Although the individual classes in the application are generic in nature (i.e., they are reusable, configurable, and context independent), the Composition Root itself is specific. It is not reusable and it sets up the context for the objects that formulate the application’s object graph. This means that when we name a method in the Composition Root, we don’t use generic names as we do in our classes. We instead use specific names.
For example, I would expect to see a method in the Composition Root called CreateMessageQueueProcessorThatWritesSubscriptionRequestsToTheDatabase. Such a method might create an instance of a general-purpose MessageQueueReader class, an instance of a general purpose MessageDeserializer class, an instance of the SubscriptionRequestRepository class, and so on.
Consider the following figure. It shows six composable classes. Arrows going outwards represent declared dependencies of the class. The arrow going inward represent the interface this class implements. We can basically use these arrows to compose objects together.
Figure 1: A representation of six composable classes
The next figure shows how a Composition Root is structured to create the application out of instances of these classes. It also shows how the Composition Root is partitioned into multiple “Create” methods.
Figure 2: A representation of the “Create” methods of some Composition Root.
This is a simple example that involves six classes only, and four methods in the Composition Root. A large application could have a much larger number of classes, and a much larger number of methods in the Composition Root.
The Composition Root as a source of SUTs
In many cases, a programmer writes unit tests that have only a single object as the SUT. This can be done easily by creating an instance of a class, passing fake objects for any dependencies that it might have, executing some public method on the object, and then verifying that the output is as expected or that a certain side effect has occurred (usually via a mock).
Some classes are a good fit for single-object testing. Classes that contain a good amount of logic, and none or a small amount of interaction with other objects, are example of such classes. For example, a class that generates prime numbers is a good candidate as a SUT of a single-object unit test.
However in other cases, a group of objects in an object-graph that represents some component, is better tested as a single unit instead of testing each individual object in the graph.
For example, you might want to test that subscription requests written to a message queue will find their way to the database. There might be many objects in the application involved in making this happen. In this case, it makes sense to test all these objects as a single unit.
Using “Create” methods to create the SUT
The methods in the Composition Root create object-graphs that represent concrete application components at different levels. By having these methods, we have actually sliced the application into many cohesive pieces. Many of these pieces represent units that are attractive to test.
Instead of creating such an object graph manually in an automated test, we can invoke a “Create” method in the Composition Root to create the object graph for us. Here is how such an automated test would look like:
[TestMethod]
public void SubscriptionRequestsWrittenToTheQueueWillFindTheirWayToTheDatabase()
{
//Arrange
var sut =
CompositionRoot
.CreateMessageQueueProcessorThatWritesSubscriptionRequestsToTheDatabase();
AddSubscriptionMessagesToTheQueue();
//Act
sut.ProcessQueueMessages();
//Assert
AssertThatSubscriptionMessagesWereWrittenToTheDatabase();
}
There are some advantages to this approach:
- It helps us test the application behavior accurately. If we create the object-graph manually in the test, it is possible that with time, the real object graph in the Composition Root will divert from the one manually created in the test itself. For example this can happen if the programmer decided to modify the real object graph in the Composition Root to extend the behavior of the component. The programmer might add a decorator to introduce caching. The test with the manually created object-graph would still test the component without caching, and there might be a bug in the caching logic or the way it was composed into the component under test, and such test would not catch the bug.
- It helps us follow the Don’t Repeat Yourself (DRY) principle. Instead of duplicating the code that creates application components in the tests, we reuse the existing code in the Composition Root.
- It helps us create tests in the concrete language of the application instead of the generic language of individual classes.
Isolation
When creating the SUT object-graph manually in the test, we can easily isolate it by injecting fake objects into the right locations when we create the object-graph. On the other hand, when we call a “Create” method in the Composition Root, it will create real dependencies directly or indirectly via sub “Create” methods.
How do we isolate the SUT from dependencies in this case?
One solution would be to make the “Create” methods take their dependencies as parameters instead of creating them via sub “Create” methods. This however, will sacrifice the navigability and readability of the Composition Root which are much more important than testability. I recommend against this approach.
As described in the “Code sharing versus Resource/state sharing” section of the Clean Composition Roots with Pure DI article, one exceptional case where it is OK to inject dependencies into “Create” methods is when injecting stateful objects, i.e., objects that hold state.
So, if in general we shouldn’t inject dependencies into “Create” methods, what is the solution then?
Using “Create” methods as isolation boundaries
Like we used “Create” methods to create SUTs, we can use them as isolation boundaries. For example, if we are using the “CreateComponent2” method (see Figure 2) to create the SUT, we can use isolation frameworks to make the “CreateComponent2SubComponent1” method return a fake object instead of the real sub component. This way, we can test Component2 in isolation of its SubComponent1.
To make this work, we first need to make sure that the Composition Root is a normal (non-static) class. In this case, all “Create” methods are instance methods.
Next, we need to make the “Create” methods that we would like to replace, e.g. “CreateComponent2SubComponent1”, both public and virtual. The virtual modifier is needed so that isolation frameworks can override these method. The public modifier is needed to make it convenient to use these methods in a lambda expression when we tell the isolation framework which method we want to configure.
Next, we use an isolation framework to create an instance of the Composition Root class in a way that allows us to override the “Create” methods that we want to return fake objects (e.g. “CreateComponent2SubComponent1”), while keeping the other methods (e.g. “CreateComponent2”) intact.
The following example shows how the Composition Root for a Console Application would look like:
class Program
{
static void Main(string[] args)
{
new CompositionRoot().Run();
}
}
public class CompositionRoot
{
public void Run()
{
CreateApplication().Run();
}
public virtual ISomeAppInterface CreateApplication()
{
//..
}
//Other "Create" methods here
}
Here is how an automated test would look like (using FakeItEasy):
[TestMethod]
public void SomeTestMethod()
{
//Arrange
var compositionRoot =
A.Fake< CompositionRoot >(options => options.CallsBaseMethods()); //By default, use the real methods of the Composition Root
var component2SubComponent1Stub = A.Fake< ISomeInterface >(); //ISomeInterface is the return type of CreateComponent2SubComponent1
A.CallTo(() => component2SubComponent1Stub.QuerySomeSubComponentData())
.Returns("Some fake data");
//Make CreateComponent2SubComponent1 return a fake sub-component
//The CreateComponent2SubComponent1 method is public
//which makes it convenient to use it to tell FakeItEasy
//which method we want to replace via a lambda expression
A.CallTo(() => compositionRoot.CreateComponent2SubComponent1())
.Returns(component2SubComponent1Stub);
var sut = compositionRoot.CreateComponent2();
//Act
var result = sut.QuerySomeComponent2Data();
//Assert
Assert.AreEqual("Some expected data", result);
}
The call to "CreateComponent2” would call the real “CreateComponent2” method. But when the “CreateComponent2SubComponent1” method is called from within the “CreateComponent2” method (see Figure 2), FakeItEasy kicks in and returns the fake component2SubComponent1Stub object.
To make this work, we need to make sure that the return type of the “CreateComponent2SubComponent1” method (and all methods that we want to replace), is abstract (e.g. an interface type). This is needed because standard isolation frameworks can fake abstract types only.
Furthermore, the CreateComponent2 method has to be made public so that we can use it from inside the test to create the SUT. So in general, all methods that we need to use to create the SUTs, must be public.
Please note that the example above doesn’t really look nice because I wanted to show all the steps needed. We can easily extract the code that creates the SUT into its own method to make the test more readable.
There is another type of isolation frameworks that allows us to do the same thing we did earlier, but without the requirement of making the “Create” methods that we want to replace non-static, virtual, and public. I don’t see this requirement as a problem, but I am going to show this alternative for the sake of completeness. Also, it is nice to learn about these frameworks since they can help with unit testing existing applications that are not designed with unit testing in mind.
.NET profiling API based isolation frameworks
Standard isolation frameworks, e.g. FakeItEasy, create fakes by creating new classes at runtime that implement the desired interfaces, and that behave in the configured way. The unit to be isolated should be designed to accept a fake object, e.g. by receiving it as a dependency via the constructor, or as we did earlier by having a virtual method that creates the dependency (so that we can override it and make it return a fake object). In other words, the ability for isolation should be part of the design.
There exists another set of isolation frameworks that allows us to substitute the behavior of methods without the need for designing for isolation required by standard isolation frameworks. These frameworks can be used to modify the behavior of any method, even if it is private or static.
How do these frameworks work?
In the .NET framework SDK, there exists an API called the profiling API. This API is designed to allow us to create unmanaged programs that monitor CLR-based programs. One thing that can be done using this API is to replace the IL code of a method before it is JIT compiled. This allows us to create isolation frameworks that can change the behavior of any method.
There already exists some isolation frameworks that use the profiling API to give us this capability. Examples of such frameworks are Microsoft Fakes, TypeMock Isolator, and Telerik JustMock. Please note that these frameworks also support the standard isolation model.
Microsoft Fakes is included in Visual Studio 2015 Enterprise, but not in the Professional or Community editions. The other two frameworks are commercial.
If we wanted, we can keep the Composition Root methods non-virtual, static and private, and use such frameworks to replace dependencies.
Here is how a test using such frameworks looks like:
[TestMethod]
public void SomeTestMethod()
{
//Arrange
var component2SubComponent1Stub = A.Fake< ISomeInterface >(); //ISomeInterface is the return type of CreateComponent2SubComponent1
A.CallTo(() => component2SubComponent1Stub.QuerySomeSubComponentData())
.Returns("Some fake data");
IComponent2 sut;
using (ShimsContext.Create())
{
ShimCompositionRoot.CreateComponent2SubComponent1 = () => component2SubComponent1Stub;
sut = CompositionRoot.CreateComponent2();
}
//Act
var result = sut.QuerySomeComponent2Data();
//Assert
Assert.AreEqual("Some expected data", result);
}
This test uses Shims from Microsoft Fakes to replace the behavior of the CreateComponent2SubComponent1 method. For more information about Shims, take a look at this reference from MSDN: https://msdn.microsoft.com/en-us/library/hh549176.aspx
It also uses FakeItEasy to create a stub for the component we want to replace.
External Dependencies
One particular type of dependency that we should care about replacing in unit tests, are external dependencies. Examples of such dependencies are databases, the file system, the system timer, and web services.
It makes a lot of sense to make sure that there exists special “Create” methods in the Composition Root that are responsible for creating these dependencies. This way, replacing such dependencies in tests would be very easy.
Application Configurations
Most applications, if not all, are customizable via configuration. Usually, there is a configuration file besides the application executable that contains all kinds of settings that can be used to customize the application behavior.
In most cases, individual classes should not talk to this configuration file directly. Instead, such settings should be read from the configuration file in the Composition Root, and then individual settings should be injected into objects that need them. This way, the application objects are not tightly coupled to the configuration file.
When writing high-level tests, we should be able to vary the configurations in the arrange phase of the test.
One way to do this is to create a method in the Composition Root called something like “GetConfigurations” that would read the configurations from the configuration file or whatever source, and then in the test we can simply change the behavior of this method to return some settings that are appropriate for the test.
Any “Create” method in the Composition Root that needs the configurations would simply call this method to obtain the configurations. Of course, the configurations themselves can be cached inside the “GetConfigurations” method when it is called for the first time.
Automated tests for class libraries
Class libraries shouldn’t contain Composition Roots, only applications should. This is true because we need to be able to compose individual objects from the class library inside the application in a certain way. For example, the application might choose to create some caching or logging decorators, and compose them with the objects from the class library.
Nevertheless, in a class library, it makes a lot of sense to create convenient factories that a simple user of the library can use to create the default composition of objects. This way, the user of the library can use the library without understanding a lot about the internal structure of the library, and the advanced user of the library has the ability to customize it in many ways.
Such convenient factories look a lot like Composition Roots and can be used as sources of SUTs also in the same way that Composition Roots can be.
Conclusion:
In this article, I discussed automated testing in general and presented an approach of automated testing that uses the Composition Root as a source or factory of SUTs. This approach can be used when creating tests that verify the behavior of components that are composed of a few or many objects. In this approach we use the “Create” methods in the Composition Root in the arrange phase of our tests to create the components that we want to test. This has the advantage of making sure that the system under test, matches the actual component as defined in the Composition Root. It also helps us follow the DRY principle and write tests in the same concrete language we use in the Composition Root.
I also showed in this article how we can isolate the SUT by using “Create” methods as isolation boundaries.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Yacoub Massad is a software developer and works mainly on Microsoft technologies. Currently, he works at Zeva International where he uses C#, .NET, and other technologies to create eDiscovery solutions. He is interested in learning and writing about software design principles that aim at creating maintainable software. You can view his blog posts at
criticalsoftwareblog.com. He is also the creator of DIVEX(
https://divex.dev), a dependency injection tool that allows you to compose objects and functions in C# in a way that makes your code more maintainable. Recently he started a
YouTube channel about Roslyn, the .NET compiler.