Unit Testing with .Net I

A brief overview of unit testing

1.0 Overview

We were taught a lot of stuff at our university — starting from the basic concepts of programming languages followed by data structure and algorithms leading up to the development of web applications.

Then I started developing personal web application projects for fun and learning purposes. But I always concentrated on developing the application itself. Finally, when I started my job, I got involved in developing web applications that follow modern industry standards.

One thing I learned from my work experience is that the development of the application itself is just a part of the entire system. An important aspect of programming is testing our code. We usually learn the concept of unit testing at universities but the topic is not prioritized as it should be.

When I interact with other programmers in our country, it becomes clearer that no matter how good a person can code, many among them always struggle to write tests for their code. I can only assume that the reason for it is our unfamiliarity with unit tests and lack of practice in writing unit tests. Because of it, we always struggle to write unit tests when we start working with production-grade applications.

During my early days of writing unit tests, one of the two questions that bothered me the most was, “how should I construct my test cases, and what should I exactly test?” The other question was, “how many scenarios do I cover before I can say that I have completed writing enough test cases for one functionality? At what point do test cases become too many test cases?”

I have been developing monolithic and microservice-based web applications with ASP.Net framework for a while now. In this and the upcoming articles, I would therefore like to discuss the basics of unit testing and show the comparison between two popular testing frameworks of DotNet — xUnit and Machine.Specifications (MSpec). I have developed a console application containing the test cases that I will discuss in this article. To easily run the application, I have dockerized the entire application. All you need is Docker Desktop and Visual Studio (Preferably 2019 or above). You can find my code here. You can clone the repository, open the solution in Visual Studio, and hit the ‘Run’ button. Docker will download the required NuGet packages and run the application. To run the test cases, open ‘Test Explorer’ and just hit the ‘Run all’ button. Both MSpec and XUnit tests should run properly.

2.0 What are Unit tests

Unit testing is a software testing process in which we logically break down our code into small sections, usually a method or a module. These small sections are known as units. We then write code to test these units.

The first question that comes to mind is, “how should I divide my codebase into these units?” To be honest, I have not come across any precise generic formula that you can follow to split the code into these logical units. It mostly depends on your business logic. Let us suppose we have written code for the following business logic in a web application : Receive an order from a customer, calculate the total price the customer should pay and return the result.

For this scenario, a completely acceptable logical separation would be to split the codebase into two parts:

a. Code that receives the order and returns a response (POST request).

b. Code that calculates the price (business logic).

However, if the code that calculates the price has a complex logic — let’s say it has a business logic that calculates the VAT based on the country to which the order will be delivered. In that case we can break the second part down to two more sections: one that calculates the total price of all the products purchased and one that calculates the VAT based on the price and country.

The best approach that I have found so far is to break down the code so that each unit is independent, has its own input and an output, and serves one independent aspect of the business case. It makes our code easier to test and helps us understand the context of our test case.

The second question that arises next is, “What should I test for these units of code?” The answer is to replicate different scenarios these units might face based on the inputs that they receive. Then we should verify that each logic of this unit is behaving as we expect and that it is returning the correct output for the provided input. We should also verify that it does not throw any unintended exceptions for any input or that the code does not break.

3.0 Purpose of Unit test

Unit testing acts as the first level of defense against bugs. Its main purpose is to identify the issues in code before the code goes to production. Since prevention is better than cure, finding a bug in a code before moving it to production reduces the cost, time, and effort to identify and fix the bug later on.

Moreover, it prevents unintentional changes to a codebase. Suppose, I have a complex code with unit tests associated with it and I have covered most of the important scenarios in the unit test. One day a developer comes along and makes some unintentional changes to my code. When he (or she) executes the unit tests, the test cases for that part of the code should fail and alert the developer about the change. Not only that but when the developer then reads the failing test cases he will be able to understand the purpose of the logic. He can then decide intentionally whether to undo the change or update the unit tests to adapt to the new changes.

Unit tests reduce the time of debugging one section of a code. If we run an application locally and add breakpoints to debug a GET request, we need to go through not only the entire method but also other methods on which the former method is dependent. But when debugging the same request via a unit test, since the context mocks dependent methods, we can test our subject faster.

To make code adaptable to unit tests, a lot of the time it needs to be restructured. This restructuring often follows some of the best practices of coding. We will see later in depth how and why we restructure a code to adapt to unit tests. But this helps a programmer improve their coding skills and reasoning ability.

4.0 Components of a Unit test

 

Some of the component components of the unit tests are as follows:

4.1 Subject

It should have an entry point to the code that we are testing. Usually, it is a method. We call this the ‘Subject’ of the test. We execute the method to run our tests.

As for example when testing the following method:

public double DividePositive(int x, int y) {

if(!IsPositive(x) || !IsPositive(y)) return 0;

return x/y;

}

This is a method that divides only positive number. In first line of the method we check if either ‘x’ or ‘y’ is positive. If they are, we return the quotient of the result. Otherwise we return a zero. Our subject will be this method that we are testing.

4.2 Input

It should have a set of different inputs that we provide to the subject in order to recreate the different scenarios.

As for example, our inputs for the above case might be to test the method works when we provide:

 

1. Two positive inputs

2. Two negative inputs

3. A positive and a negative input

4. A numerator which is larger than the denominator

5. A numerator which is smaller than the denominator

6. A non-zero numerator and a zero denominator

7. A zero numerator and a non-zero denominator

4.3 Output

The method will provide a set of outputs for the set of inputs. We should verify that the outputs that we received are the ones that we expected.

4.4 Context

A context is an environment that we create to run our test. One of the best practices for ‘unit testing’ is to keep our subject as isolated as possible. The subject should be able to run successfully in the context we create. We should try to remove the dependencies as much as possible when we create the context. For example, we can notice that our subject is dependent on the output of the method ‘IsPositive’. When we test our subject, we should create a context so that this dependency will be removed and the subject can execute without the method ‘IsPositive’ being executed.

The reason for this approach is that, while testing our subject we should not be concerned about how the logic of other dependencies is being executed. It makes the test simpler and decreases the number of scenarios that we need to test. All we need to be concerned about is, what type of output the dependency is expected to return for the input that our subject provides.

For example, in this case, we can create the following context when the input ‘x’ is positive and the input ‘y’ is negative:

1. When the test executes ‘IsPositive(x)’ return ‘true’.

2. When the test executes ‘IsPositive(y)’ return ‘false’.

In our context, we have defined what the output of ‘IsPositive’ should be for ‘x’ and what it should be for ‘y’. So while testing, when the method ‘IsPositive(x)’ is executed, it will immediately return ‘true’ instead of actually executing the method. This is known as ‘Mocking’ a method. It helps us remove dependencies of the subject and only test the logic inside the subject. In the same way we can mock class objects that our subject uses to perform a task.

5.0 Structure of a Unit test

A unit test generally has three parts. When a unit test starts executing, it executes in the following sequence:

5.1 Arrange

This is the part where we create the context for our unit tests. We mock the methods which are out of the scope of our test, we mock dependency and inject those mock dependencies to our subject so that the subject uses our mock instead of the actual ones. In this way we can control what the mock should return without being executed. We also define what our mocks should return when a specific method of the mock is executed. When a unit test starts executing it first creates the context of the test. In xUnit we call this ‘Arrange’ and in MSpec we call this ‘Establish’.

5.2 Act

This is the part where unit test executes the method or subject that we are testing. In xUnit this is known as ‘Act’ and in MSpec this is known as ‘Because’.

5.3 Assert

In this part we verify that for each input the subject returns the output that we expect. It also verifies that the mock methods which were supposed to be executed for the input, was actually executed with the desired inputs provided to the mock methods. For our above example, we can verify that the ‘IsPositive’ was executed twice — once with ‘x’ as the input and a second time with ‘y’ as the input.

6.0 Conclusion

In this article, we have gone through the concept, purpose, and structure of unit tests. In the next articles, we will go through a simple .NET console application that fetches the contents of a web page and displays it in the console. It uses an Http request to fetch the data. We will go through different interfaces that we create for the application and the purpose of those interfaces. Finally, we will go through the xUnit and MSpec unit tests that I have written for the application and compare both testing frameworks. I hope you enjoy reading the article and find it useful. Happy coding!

References