How to Fully Cover .NET C# Console Application With Unit Tests
top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

  • Writer's pictureAhmed Tarek

How to Fully Cover .NET C# Console Application With Unit Tests

Updated: Apr 17

Best Practice to achieve 100% coverage using Test Driven Development (TDD), Dependency Injection (DI), Inversion of Control (IoC), and IoC Containers.


Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Annie Spratt on Unsplash, modifed by Ahmed Tarek

Some colleagues of mine are complaining that sometimes they are not able to apply TDD or write unit tests for some modules or applications, Console Applications is one of these.


How could I test a Console application when the input is passed by key strokes and the output is presented on a screen?!!


Actually this happens from time to time, you find yourself trying to write unit tests for something you seem to not have any control upon.


 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Sangga Rima Roman Selia on Unsplash

Misconception


The truth is, you just missed the point. You don’t need to test the “Console” application, you want to test the business logic behind it.


When you are building a Console application, you are building an application for someone to use, he expects to pass some inputs and get some corresponding outputs, and that’s what you really need to test.


You don’t want to test the System.Console static class, this is a built-in class that is included in the .NET framework and you have to trust Microsoft on this.


Now, you need to think how to separate these two areas into separate components or modules so that you can start writing tests for the one you desire without interfering with the other one, and this is what I am going to explain to you…


 


 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Mark Fletcher-Brown on Unsplash

The Idea


First, let’s come up with a stupid simple Console application idea and use it as an example to apply on.


First, you have this simple menu.


Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering

When you choose option 1 and enter your name, you get the Hello message as in the image below. Hitting enter would close the application.


Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering

When you choose option 2 and enter your name, you get the Goodbye message as in the image below. Hitting enter would close the application.


Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering

Too simple, right? Yes, I agree with you. However, let’s assume that the UI, strings, characters and everything you see on the screen, is part of the requirements.


This means that if you are going to write unit tests, this should also be covered in a way that a minor change on a single character in the production code, should trigger a failing unit test.


 


 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Brett Jordan on Unsplash

This is our plan:


  1. Build the Console application in a traditional bad way.

  2. See if we can write automated unit tests or not.

  3. Re-implement the Console application in a good way.

  4. Write some unit tests.


 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Mehdi on Unsplash

The Bad Way


Simply, do everything in one place.



What we can notice here:

  1. Everything is in one place.

  2. We are directly using the static System.Console class.

  3. We can’t test the business logic without bumping into System.Console.


 


 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Brett Jordan on Unsplash

Trying to Write Unit Tests


Really? are you really expecting to be able to write a unit test for that code?


Here are the challenges:

  1. Depending on static classes like System.Console.

  2. Can’t define and isolate dependencies.

  3. Can’t replace dependencies with Mocks or Stubs.


If you can do something about it, you are a hero… believe me.


 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Volkan Olmez on Unsplash

The Good Way


Now, let’s split our solution into smaller modules.


Console Manager

This is the module which is responsible for providing the functionality we need from the Console… any console.


This module would consist of two parts:

  1. Abstractions.

  2. Implementations.


Therefore we will have the following:

  1. IConsoleManager: This is the interface defining what we are expecting from any Console Manager.

  2. ConsoleManagerBase: This is the abstract class implementing IConsoleManager and providing any common implementations between all Console Managers.

  3. ConsoleManager: This is the default Console Manager implementation which wraps System.Console and is actually used at runtime.





What we can notice here:

  1. Now we have IConsoleManager.

  2. We can use Mocks and Stubs to replace IConsoleManager while writing unit tests.

  3. For the common base class ConsoleManagerBase we are not providing any common implementation to be used by children.

  4. I know this is not the best thing to do, however, I am doing it here just as a reminder to you that this option is there and you can use it whenever needed.


 

Program Manager

This is the module which is responsible for providing the main application functionality.


This module would consist of two parts:

  1. Abstractions.

  2. Implementations.


Therefore we will have the following:

  1. IProgramManager: This is the interface defining what we are expecting from any Program Manager.

  2. ProgramManagerBase: This is the abstract class implementing IProgramManager and providing any common implementations between all Program Managers.

  3. ProgramManager: This is the default Program Manager implementation which is actually used at runtime. It also depends on the IConsoleManager.





What we can notice here:

  1. Now we have our dependency of ProgramManager on IConsoleManager well defined.

  2. We have IProgramManager and we can use mocks and stubs to replace IProgramManager while writing unit tests.

  3. For the common base class ProgramManagerBase we are not providing any common implementation to be used by children.

  4. I know this is not the best thing to do, however, I am doing it here just as a reminder to you that this option is there and you can use it whenever needed.


 


 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Goh Rhy Yan on Unsplash

Disclaimer


The ProgramManager class could be split into smaller parts. That would make it easier to track and cover with unit tests. However, this is something I am leaving to you to do.


 

Console Application

This is the main application.


Here we are going to use Ninject as our IoC container. It is simple to use and you can always check their online documentation.


On the main Console Application project, we would create NinjectDependencyResolver.cs file. This file would be as follows.



What we can notice here:

  1. The NinjectDependencyResolver class is inheriting NinjectModule.

  2. We are overriding the void Load() method where we are setting our bindings as expected.


Now, on the Program.cs:



What we can notice here:

  1. We are depending on the IProgramManager.

  2. We created the IoC container through var kernel = new StandardKernel();.

  3. Then we loaded the dependencies into the IoC container through kernel.Load(Assembly.GetExecutingAssembly());. This instructs Ninject to get its bindings from all classes inheriting NinjectModule inside the current assembly/project.

  4. This means that the bindings would come from our NinjectDependencyResolver class as it is inheriting NinjectModule and located inside the current assembly/project.

  5. To get an instance of IProgramManager we are using the IoC container as follows kernel.Get<IProgramManager>();.


Now, let’s see if this design and work we have done till this moment has fixed our problem.


 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Markus Winkler on Unsplash

Moment of Truth


So, the question now is, can we cover our Console Application with unit tests? To answer this question, let’s try to write some unit tests…


Stubs or Mocks

If you have some experience with unit testing, you should know that we have Stubs and Mocks to be used to replace our dependencies.


Just for fun, I would use stubs for our example here.

So, I would define ConsoleManagerStub as a stub for IConsoleManager as follows:



 


 

And finally the unit tests would be as follows:





 

Best Practice Cover DotNet (.NET) CSharp (C#) Application with unit tests with Test Driven Development TDD Dependency Injection DI Inversion of Control IoC Containers. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by david Griffiths on Unsplash

Finally

Now we have been able to cover our Console Application with unit tests. However you might think that this is too much for a simple application like the one we have here. Isn’t this an overkill?


Actually, it depends on what you want to cover. For example, in our simple application, I dealt with every character on the UI as a requirement which should be covered by unit tests. So, if you go and change a character on the main implementation, a unit test would fail.


May be in your case it would be different. However, it would always be good that you know how to do it even down to the smallest character.


That’s it, hope you found reading this article as interesting as I found writing it.



Recent Posts

See All

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

bottom of page
Mastodon Mastodon