Interfaces, Testing and Dependency Injection in C#

Search for a command to run...

No comments yet. Be the first to comment.
A series of articles imported from my previous blog hosted at https://www.talkingitthrough.co.uk.
Language integrated queries are a very simple but powerful way to manage custom collections. Whilst this is only one use for LINQ (it can be used for database connection and all manner of other data driven queries), it is probably the way I use it mo...
Introduction Semantic Kernel is an open-source framework by Microsoft that integrates machine learning and natural language processing to build AI applications. It offers tools for tasks like language understanding, text generation, and semantic sear...

Introduction In the earlier articles in my PDF Splitter series, we have relied on the inbuild .NET libraries to handle working with PDF files in MAUI, but these only support opening and displaying of PDF files. Now that we are looking to save our new...

Introduction In the previous article, we created a PDF model and service to allow us to work with and track PDF pages. The next step is to build the UI to allow us to interact with the service. To do this we will create a single 'view' and a 'view mo...

Introduction For the fourth article in my PDF Splitter series, we will start working with PDF files in .NET MAUI. Helpfully, MAUI contains a lot of useful PDF tools for reading PDF files out of the box, so we do not need to use any additional librari...

Introduction In this article, I'm going to introduce basic navigation into my .NET MAUI PDF Splitter application. If you're following my PDF Splitter Application series, you will have a simple application based on the out-the-box .NET template with a...

When I first started out as developer, the usefulness of interfaces was one of the things I never quite worked out. However, as I got more into design and testing then everything suddenly became much more clear. By developing to interfaces we can make our applications much more extendable, easier to test, and making changes to the functionality much easier, simply by removing the dependency on a particular service.
Here I will demonstrate using interfaces to support testing through the use of dependency injection. To do this, I will use the principle of a simple logger. Imagine we have written an application with a logger that logs errors to a file system or database.
class Log
{
public void WriteMessage(string message)
{
// Write message out to database or file system
}
}
Now, if we have created a dependency on the logger itself we straight away hit two problems. Firstly, we may need the file path or database to be in place before we can run our application. Secondly, we cannot write a unit test for this application as a unit test should explicitly test the code, and not the integration with the file system/database. Further to this, we also cannot test the code is working without checking the output to see if something is written.
class DoWorkClassA
{
public void DoWorkClassA(Log log)
{
int a = 1;
int b = 2;
if (a != b)
{
log.WriteMessage("Value mismatch");
}
}
}
The solution to this is to extrapolate an interface from our logger and use this as our dependency instead, as follows:
interface ILog
{
void WriteMessage(string message);
}
We can then change our class objects to use the interface rather than the log object itself.
public void DoWorkClassA(ILog log)
{
int a = 1;
int b = 2;
if (a != b)
{
log.WriteMessage("Value mismatch");
}
}
Now if we write a unit test [or want to provide alternate logger implementations to make development easier], it’s as simple as providing a second implementation of the ILog interface and injecting this into our classes.
class LogForTesting : ILog
{
public void WriteMessage(string message)
{
Console.WriteLine(message);
}
}
Now, we can write a test, and inject our test log into the class we’re testing, so we can run the test without the need for the filesystem, the database or to use the ‘real’ log implementation that would be with the release version. In this example, also gives us the added benefit we can output what our log is doing to the console so can see in real-time what our application is logging.
\[TestFixture\]
class Test
{
ILog log;
\[TestFixtureSetUp\]
public void SetUp()
{
this.log = new LogForTesting();
}
\[Test\]
public void TestClassADoesWork()
{
DoWorkClassA classA = new DoWorkClassA(this.log);
// Do test
}
}
The post Interfaces, Testing and Dependency Injection appeared first on Talking I.T. Through.