Test-Driven Development in C# .Net

Introduction 

 
Hello all, welcome to Test Driven Development. I’m Abdul Rahman, a Senior Software developer, and a freelance solution architect. I used to develop products/enterprise applications using C# and .Net.
 
In this article, we will learn how to use TDD in C# to develop requirements in .NET applications. TDD is a very powerful approach to build robust software. Before we develop the feature, we will write a unit test for the feature which means the unit test will drive our feature development.
 
Let’s cover:
  1. Basics of Test-Driven Development
  2. Using TDD to write business logic
  3. Decoupling dependencies
By the end of this article, you will learn how to implement TDD in your .NET web application.
 
Prerequisites
 
You need to know the basics of C# and unit testing. You can find the link for source code and follow along with me.
 

Basics of Test-Driven Development

 
Let’s see what is test-driven Development and explain to you the project scenario.
 
Test-Driven Development, or TDD for short, is a method used to write tests before we start our implementation. Before you start, you might list the requirements that need to be fulfilled in your application.
 
Test Driven Development In C# .Net
 
Then you take the first requirement and write a failing test. The test fails and it is RED as you haven’t developed it yet. But the test describes already what your code should do to fulfill the requirement. Now you need to make the test GREEN by writing the necessary code to make the test pass. After you write the code to make the test green you need to Refactor the code. Maybe you can simplify the code or extract few code lines into a method to make code more readable and maintainable. After you are done with the first requirement, you can continue with the next requirement. This means you iterate the entire cycle (Red -> Green -> Refactor) for another requirement and so on. So the tests are driving your development. This is the heart of TDD and known as TDD cycle.
 
TDD means writing tests to implement a requirement and continuously iterate through RED GREEN and REFACTOR cycles.

Test Driven Development In C# .Net
 
Advantages
 
Test Driven Development In C# .Net
  1. TDD makes you think with the needed API from the beginning. You need to think about what classes, properties, API’s are needed. This will usually lead to great API design.
  2. After you know the class and properties, another big advantage is that you need to think about what the code should do than how it should do. As you start with test you don’t need to have any idea about implementation. You just need to write a test for what the code should do. After writing the test you can think of requirements and their development.
  3. While thinking of your requirements, you get quick feedback about your requirements by running the test. The fact that you get quick feedback means you even don’t need a fully working application at all. You just need a class library to build your business logic and don’t need the entire project.
  4. This helps you create modular code. You can decouple the dependencies from the beginning and TDD makes you do that from the beginning. This decoupling of dependency makes you write a modular code by isolating the dependencies like a database that is not ready yet and the web API isn't ready when beginning the development.
  5. This leads to a maintainable codebase, as you will have one test per requirement. You can write code to add new functionality and run all the unit tests to ensure that the existing code doesn’t break. You can be confident about your new code as well as the existing code.
  6. These tests will serve as good documentation. For example, the test for the code written by others will help you understand why the code has been written.
Disadvantage
 
The only disadvantage is that TDD is not so easy to start by writing tests for beginners. In fact, TDD is an art that every developer should master.
 
Scenario
 
Let’s take a simple scenario where a user needs to book a ticket. The user needs to fill a form with basic details to book the ticket.
 
Ticket Booking Solution
TicketBookingCore (TicketBookingRequestProcessor) (.Net Core Class Library)
TicketBookingCore.Tests (TicketBookingRequestProcessorTests) (XUnit test project)
  • Getting started with TDD
  • Testing and implementing business logic.
  • Adding features in asp.net core app.
Let’s first start with creating a test project named TicketBookingCore.Test for business logic. We will start writing the first failing test and continue from that.
Then let’s work on adding additional business logic by implementing tests and iterating through the TDD cycle. We have more complex requirements that will force us to decouple dependencies and we need to mock those classes while writing the test.
 
Finally let’s see how to implement TDD with web project. It is your responsibility to check the user entered information and implement TDD.
 

Using TDD to write business logic

 
Requirements
1. Response should contain the same values as request after booking.
2. Booking should be saved to database.

Understand the First Requirement

 
The user will submit a form to book a ticket which will make a call to TicketBookingRequestProcessor to book a ticket. The processor must return the same data after the booking is successful. To do this lets first think of the API. The TicketBookingRequestProcessor will use Book to book a ticket and it will receive TicketBookingRequest as input and return TicketBookingResponse as result.
 
Test Driven Development In C# .Net
 
This simple requirement is good to start with TDD.
 

Create a Red unit test

  1. Create a new C# XUnit test project named TicketBookingCore.Tests.
  2. As you need to test TicketBookingProcessor Class, create a new class named TicketBookingRequestProcessorTests.
  3. Create a first test method as ShouldReturnTicketBookingResultWithRequestValues.
  4. Mark the method with [Fact] attribute to indicate it as a test.
  5. Now create a processor instance as TicketBookingRequestProcessor and press Ctrl + . to create a class in a new file.
Code
  1. namespace TicketBookingCore.Tests  
  2. {  
  3.     public class TicketBookingRequestProcessorTests  
  4.     {  
  5.         [Fact]  
  6.         public void ShouldReturnTicketBookingResultWithRequestValues()  
  7.         {  
  8.             var processor = new TicketBookingRequestProcessor();  
  9.         }  
  10.     }  

Create a TicketBookingRequest with FirstName, LastName and Email properties and set the values and again press Ctrl + . to create the class in new file.
 
Code
  1. [Fact]  
  2. public void ShouldReturnTicketBookingResultWithRequestValues()  
  3. {  
  4.     var processor = new TicketBookingRequestProcessor();  
  5.   
  6.     var request = new TicketBookingRequest  
  7.     {  
  8.         FirstName = "Abdul",  
  9.         LastName = "Rahman",  
  10.         Email = "abdulrahman@demo.com"  
  11.     };  

Add the following line TicketBookingResponse response = processor.Book(request); and press Ctrl + . to generate the Book method in TicketBookingRequestProcessor class.
 
Next, we need to assert if the input and output are equal.
 
To Assert the input and output, first, create TicketBookingResponse class with the same properties as TicketBookingRequest.
 
Modify the Book Method in TicketBookingRequestProcessor to return TicketBookingResponse.
 
Code
  1. internal class TicketBookingRequestProcessor  
  2. {  
  3.     public TicketBookingRequestProcessor()  
  4.     {  
  5.     }  
  6.   
  7.     internal TicketBookingResponse Book(TicketBookingRequest request)  
  8.     {  
  9.       throw new NotImplementedException();  
  10.     }  

We are all set with AAA (Arrange, Act, and Assert) of unit tests.
 
Code
  1. [Fact]  
  2. public void ShouldReturnTicketBookingResultWithRequestValues()  
  3. {  
  4.     // Arrange  
  5.     var processor = new TicketBookingRequestProcessor();  
  6.   
  7.     var request = new TicketBookingRequest  
  8.     {  
  9.         FirstName = "Abdul",  
  10.         LastName = "Rahman",  
  11.         Email = "abdulrahman@demo.com"  
  12.     };  
  13.   
  14.     // Act  
  15.     TicketBookingResponse response = processor.Book(request);  
  16.   
  17.     // Assert  
  18.     Assert.NotNull(response);  
  19.     Assert.Equal(request.FirstName, response.FirstName);  
  20.     Assert.Equal(request.LastName, response.LastName);  
  21.     Assert.Equal(request.Email, response.Email);  

Press Ctrl + E, T to open Test Explorer. You can now see the test is listed in the test explorer. Now Click on the Run (Green Triangle) button. You can see that the test fails with NotImplementedException.
 
Test Driven Development In C# .Net
 
Great, we completed the 1st step of TDD (Red Phase).
 

Write Code to make test Green

 
Now let’s go to 2nd step of TDD (Green Phase) and write the bare minimum code required to make the test pass.
 
Implemented the below code in Book Method of TicketBookingRequestProcessor class.
 
Code
  1. internal TicketBookingResponse Book(TicketBookingRequest request)  
  2. {  
  3.     return new TicketBookingResponse  
  4.     {  
  5.         FirstName = request.FirstName,  
  6.         LastName = request.LastName,  
  7.         Email = request.Email  
  8.     };  

That’s it. Now again run the test. The test passes and turns green.
 
Test Driven Development In C# .Net
 
Great, we completed the 2nd step of TDD (Green Phase).
 

Refactor to improve the code

 
Now we are in the 3rd step of TDD (Refactor Phase). We can refactor and improve the code as follows,
  1. Create New .Net Core Class Library Named TicketBookingCore and move TicketBookingRequest, TicketBookingRequestProcessor and TicketBookingResponse into this project.
  2. Fix Namespaces of the files in TicketBookingCore project.
  3. Change the access modifier of all classes and methods to the public.
  4. Create a base class TicketBookingBase and move the properties from TicketBookingRequest and TicketBookingResponse and inherit from TicketBookingBase class.
  5. Add a reference to TicketBookingCore project in the TicketBookingCore.Tests project.
  6. Now Build the solution and run the tests again.
  7. The test should pass.
The new project structure should look like shown below:
 
Test Driven Development In C# .Net
 
Great, we completed the 3rd step of TDD (Refactor Phase).
 
Now let’s write another test using TDD to quickly verify that request is not null while calling a Book method.
 
Code
  1. [Fact]  
  2. public void ShouldThrowExceptionIfRequestIsNull()  
  3. {  
  4.     // Arrange  
  5.     var processor = new TicketBookingRequestProcessor();  
  6.   
  7.     // Act  
  8.     var exception = Assert.Throws<ArgumentNullException>(() => processor.Book(null));  
  9.   
  10.     // Assert  
  11.     Assert.Equal("request", exception.ParamName);  

Now if you run the above from test explorer, the test will fail with NullReferenceException instead of ArgumentNullException, as shown below:
 
Test Driven Development In C# .Net
 
Now let’s write the minimum required code to make the test pass.
 
Code
  1. public TicketBookingResponse Book(TicketBookingRequest request)  
  2. {  
  3.     if (request is null)  
  4.     {  
  5.         throw new ArgumentNullException(nameof(request));  
  6.     }  
  7.   
  8.     return new TicketBookingResponse  
  9.     {  
  10.         FirstName = request.FirstName,  
  11.         LastName = request.LastName,  
  12.         Email = request.Email  
  13.     };  

Now run the test again and the test will pass.
 
Test Driven Development In C# .Net
 
The next step is to refactor. We don’t have anything in Book method to refactor but that doesn’t mean that we have nothing we can also refactor our tests.
 
Since we need TicketBookingRequestProcessor in both the test, we can remove that from both the test and move it to TicketBookingRequestProcessorTests constructor and use that in our test methods.
 
Code
  1. public class TicketBookingRequestProcessorTests  
  2. {  
  3.     private readonly TicketBookingRequestProcessor _processor;  
  4.   
  5.     public TicketBookingRequestProcessorTests()  
  6.     {  
  7.         _processor = new TicketBookingRequestProcessor();  
  8.     }  
  9.   
  10.     [Fact]  
  11.     public void ShouldReturnTicketBookingResultWithRequestValues()  
  12.     {  
  13.         // Arrange  
  14.         var request = new TicketBookingRequest  
  15.         {  
  16.             FirstName = "Abdul",  
  17.             LastName = "Rahman",  
  18.             Email = "abdulrahman@demo.com"  
  19.         };  
  20.   
  21.         // Act  
  22.         TicketBookingResponse response = _processor.Book(request);  
  23.   
  24.         // Assert  
  25.         Assert.NotNull(response);  
  26.         Assert.Equal(request.FirstName, response.FirstName);  
  27.         Assert.Equal(request.LastName, response.LastName);  
  28.         Assert.Equal(request.Email, response.Email);  
  29.     }  
  30.   
  31.     [Fact]  
  32.     public void ShouldThrowExceptionIfRequestIsNull()  
  33.     {  
  34.         // Act  
  35.         var exception = Assert.Throws<ArgumentNullException>(() => _processor.Book(null));  
  36.   
  37.         // Assert  
  38.         Assert.Equal("request", exception.ParamName);  
  39.     }  

Understand the second Requirement

 
Now we need to save the booking to the database. To save to database we need to modify the book method to save the booking to the database and return the booking request values.
 
Test Driven Development In C# .Net
 

Decouple the Dependencies

 
If you look at the above image, TicketBookingRequestProcessor has too many responsibilities. One is to process the booking request and another is to save it to the database.
 
But this violates the Single Responsibility Principle (SRP) – which says a class should have a single responsibility. So, to adhere to the SRP principle, we need to move the save to database logic to a separate class like TicketBookingRepository.
 
Test Driven Development In C# .Net
 
We can now save the TicketBooking object to the database using the TicketBookingRepository class. But now TicketBookingRequestProcessor depends on the TicketBookingRepository class to save to the database. This is not for the unit test, as the test needs to run in isolation. So here comes the Dependency Inversion Principle (DI), which says a class should always depend on abstraction, not on implementation. We can implement this by introducing a new interface ITicketBookingRepository. This interface implements TicketBookingRepository and saves to the database.
 
Test Driven Development In C# .Net
 
So now TicketBookingRequestProcessor depends on ITicketBookingRepository and we don’t need to worry about the database. This means to write a test we can create a mock (fake) object and use that to save to database and we can verify by this from the mock object that Save method is called at least once. This is how we can use interface to decouple the dependencies.
 
Now let’s create a failing Red unit test,
  1. Create a new test method ShouldSaveToDatabase.
  2. Create a request object and pass it to the processor save method.
Code
  1. [Fact]  
  2. public void ShouldSaveToDatabase()  
  3. {  
  4.     // Arrange  
  5.     var request = new TicketBookingRequest  
  6.     {  
  7.         FirstName = "Abdul",  
  8.         LastName = "Rahman",  
  9.         Email = "abdulrahman@demo.com"  
  10.     };  
  11.   
  12.     // Act  
  13.     TicketBookingResponse response = _processor.Book(request);  

Now to save to database, we need ITicketBookingRepository and that needs to be injected to TicketBookingRequestProcessor.
  1. Add ITicketBookingRepository to TicketBookingCore project
  2. Add Save() method with TicketBooking object as a parameter.
  3. Press Ctrl + . to create a TicketBooking class and inherit from the TicketBookingBase class.
Code
  1. public interface ITicketBookingRepository  
  2. {  
  3.     void Save(TicketBooking ticket);  

Now we need a mock object for ITicketBookingRepository. We can use the mock library to fake the repository.
  1. Add _ticketBookingRepositoryMock = new Mock<ITicketBookingRepository>(); to TicketBookingRequestProcessorTests constructor.
  2. press Ctrl + . to download install Moq nuget package and repeat to add a private readonly field Mock<ITicketBookingRepository> _ticketBookingRepositoryMock for that repository
  3. Pass the field to TicketBookingRequestProcessor constructor and press Ctrl + . to add that as a parameter to TicketBookingRequestProcessor class.
Code
  1. public class TicketBookingRequestProcessorTests  
  2. {  
  3.     private readonly Mock<ITicketBookingRepository> _ticketBookingRepositoryMock;  
  4.     private readonly TicketBookingRequestProcessor _processor;  
  5.   
  6.     public TicketBookingRequestProcessorTests()  
  7.     {  
  8.         _ticketBookingRepositoryMock = new Mock<ITicketBookingRepository>();  
  9.         _processor = new TicketBookingRequestProcessor(_ticketBookingRepositoryMock.Object);  
  10.     }  
  11. }  
  12.   
  13. public class TicketBookingRequestProcessor  
  14. {  
  15.     public TicketBookingRequestProcessor(ITicketBookingRepository ticketBookingRepository)  
  16.     {  
  17.     }  

Now we need to write a setup in mock repository to make a callback when Save is called in that repository. And we need to assert that the Save method in the repository is called at least once and verifies the properties in the callback object.
 
Code
  1. [Fact]  
  2. public void ShouldSaveToDatabase()  
  3. {  
  4.     // Arrange  
  5.     TicketBooking savedTicketBooking = null;  
  6.   
  7.     _ticketBookingRepositoryMock.Setup(x => x.Save(It.IsAny<TicketBooking>()))  
  8.         .Callback<TicketBooking>((ticketBooking) =>  
  9.         {  
  10.             savedTicketBooking = ticketBooking;  
  11.         });  
  12.   
  13.     var request = new TicketBookingRequest  
  14.     {  
  15.         FirstName = "Abdul",  
  16.         LastName = "Rahman",  
  17.         Email = "abdulrahman@demo.com"  
  18.     };  
  19.   
  20.     // Act  
  21.     TicketBookingResponse response = _processor.Book(request);  
  22.   
  23.     // Assert  
  24.     _ticketBookingRepositoryMock.Verify(x => x.Save(It.IsAny<TicketBooking>()), Times.Once);  
  25.   
  26.     Assert.NotNull(savedTicketBooking);  
  27.     Assert.Equal(request.FirstName, savedTicketBooking.FirstName);  
  28.     Assert.Equal(request.LastName, savedTicketBooking.LastName);  
  29.     Assert.Equal(request.Email, savedTicketBooking.Email);  

Now we are done with test setup. Run the test and the test should fail.
 
Test Driven Development In C# .Net
 

Writing code to make the test pass

 
Now we need to write the minimum code to make the test pass which is to create a private readonly field for ITicketBookingRepository make a call to Save method in repository inside the Book method in processor class.
 
Code
  1. public class TicketBookingRequestProcessor  
  2. {  
  3.     private readonly ITicketBookingRepository _ticketBookingRepository;  
  4.   
  5.     public TicketBookingRequestProcessor(ITicketBookingRepository ticketBookingRepository)  
  6.     {  
  7.         _ticketBookingRepository = ticketBookingRepository;  
  8.     }  
  9.   
  10.     public TicketBookingResponse Book(TicketBookingRequest request)  
  11.     {  
  12.         if (request is null)  
  13.         {  
  14.             throw new ArgumentNullException(nameof(request));  
  15.         }  
  16.   
  17.         _ticketBookingRepository.Save(new TicketBooking  
  18.         {  
  19.             FirstName = request.FirstName,  
  20.             LastName = request.LastName,  
  21.             Email = request.Email  
  22.         });  
  23.   
  24.         return new TicketBookingResponse  
  25.         {  
  26.             FirstName = request.FirstName,  
  27.             LastName = request.LastName,  
  28.             Email = request.Email  
  29.         };  
  30.     }  

Now if you run the test, the test will pass.
 
Test Driven Development In C# .Net
 

Refactor the code

 
Now we can improve the code by doing some refactoring.
  1. According to Do Not Repeat Principle (DRY), we should avoid repeating the same code
  2. In the TicketBookingRequestProcessorTests class, the same TicketBookingRequest object is constructed and used in two methods. This can be moved to the TicketBookingRequestProcessorTests constructor and can be made as a private readonly field.
  3. In the TicketBookingProcessor class, we can see that the mapping of properties is done twice. This mapping can be extracted into a generic method.
Now your code should look like as follows:
 
Code
  1. public class TicketBookingRequestProcessorTests  
  2. {  
  3.     private readonly TicketBookingRequest _request;  
  4.     private readonly Mock<ITicketBookingRepository> _ticketBookingRepositoryMock;  
  5.     private readonly TicketBookingRequestProcessor _processor;  
  6.   
  7.     public TicketBookingRequestProcessorTests()  
  8.     {  
  9.         _request = new TicketBookingRequest  
  10.         {  
  11.             FirstName = "Abdul",  
  12.             LastName = "Rahman",  
  13.             Email = "abdulrahman@demo.com"  
  14.         };  
  15.   
  16.         _ticketBookingRepositoryMock = new Mock<ITicketBookingRepository>();  
  17.   
  18.         _processor = new TicketBookingRequestProcessor(_ticketBookingRepositoryMock.Object);  
  19.     }  
  20.   
  21.     [Fact]  
  22.     public void ShouldSaveToDatabase()  
  23.     {  
  24.         // Arrange  
  25.         TicketBooking savedTicketBooking = null;  
  26.   
  27.         _ticketBookingRepositoryMock.Setup(x => x.Save(It.IsAny<TicketBooking>()))  
  28.             .Callback<TicketBooking>((ticketBooking) =>  
  29.             {  
  30.                 savedTicketBooking = ticketBooking;  
  31.             });  
  32.   
  33.         // Act  
  34.         _processor.Book(_request);  
  35.   
  36.         // Assert  
  37.         _ticketBookingRepositoryMock.Verify(x => x.Save(It.IsAny<TicketBooking>()), Times.Once);  
  38.   
  39.         Assert.NotNull(savedTicketBooking);  
  40.         Assert.Equal(_request.FirstName, savedTicketBooking.FirstName);  
  41.         Assert.Equal(_request.LastName, savedTicketBooking.LastName);  
  42.         Assert.Equal(_request.Email, savedTicketBooking.Email);  
  43.     }  
  44. }  
  45.   
  46. public class TicketBookingRequestProcessor  
  47. {  
  48.     private readonly ITicketBookingRepository _ticketBookingRepository;  
  49.   
  50.     public TicketBookingRequestProcessor(ITicketBookingRepository ticketBookingRepository)  
  51.     {  
  52.         _ticketBookingRepository = ticketBookingRepository;  
  53.     }  
  54.   
  55.     public TicketBookingResponse Book(TicketBookingRequest request)  
  56.     {  
  57.         if (request is null)  
  58.         {  
  59.             throw new ArgumentNullException(nameof(request));  
  60.         }  
  61.   
  62.         _ticketBookingRepository.Save(Create<TicketBooking>(request));  
  63.   
  64.         return Create<TicketBookingResponse>(request);  
  65.     }  
  66.   
  67.     private static T Create<T>(TicketBookingRequest request) where T : TicketBookingBase, new ()  
  68.     {  
  69.         return new T  
  70.         {  
  71.             FirstName = request.FirstName,  
  72.             LastName = request.LastName,  
  73.             Email = request.Email  
  74.         };  
  75.     }  

Now run all the tests. All the tests should pass.
 
Test Driven Development In C# .Net
 

Summary

 
In this article, we learned how to implement TDD in C# .Net applications. We learned the TDD principle, advantages and disadvantages of TDD, understanding the requirements and starting from the test project then slowly building the actual requirement. We also learned how to decouple dependencies and mock them in a unit test. TDD is all about iterating the RED, GREEN and Refactor cycle over and again to develop our requirement. This is a demo project and it has a lot of scope for improvement. We learned TDD with XUnit Project, but the same can be applied to NUnit or MSTest projects as well.
 
Here is the link to the source code.