C# interfaces are great, and since C# 8.0 they’re even cooler than they were before. Personally, I find them great for unit testing since I can use dependency injection to have my code reference a vital object by its interface, then pass in either a real or simulated version of that object depending on whether this is a unit test or real use. But how are we supposed to simulate a class that doesn’t implement an interface? I have just the trick!
Ideal simulation situation (no tricks needed)
The code below is an example of the ideal situation where you’re using a class Library.Foo
that already implements an interface Library.IFoo
. Our application’s method MyAppCode.UseFoo.CallBar
takes a Library.IFoo
instance called usuallyFoo
as input. During normal execution of the application usuallyFoo
will be an instance of Library.Foo
, but during unit testing we’ll pass in an instance of our custom MyTestCode.SimFoo
class instead.
namespace Library
{
public interface IFoo
{
void Bar();
}
public class Foo : IFoo
{
public void Bar()
{
// library implementation
}
}
}
namespace MyAppCode
{
public static class UseFoo
{
public static void CallBar(Library.IFoo usuallyFoo)
{
usuallyFoo.Bar();
}
}
}
namespace MyTestCode
{
class SimFoo : Library.IFoo
{
public void Bar()
{
// simulated implementation
}
}
static class TestUseFoo
{
public static void TestCallBar()
{
MyAppCode.UseFoo.CallBar(new SimFoo());
}
}
}
Time to pretend
I discovered this trick because my current job involves the System.IO.Ports.SerialPort
class a lot, and that class does not implement an interface conducive for testing. Rather than a contrived abstract example with more Foo
and Bar
, let’s base this section on the real SerialPort
class.
First, we need to know what members of the SerialPort
class our application is using. If our code looks like the code below, then we need three members: IsOpen
, Open()
, and ReadLine()
.
using System.IO.Ports;
namespace MyAppCode
{
public static class MyClass
{
public static string ReadFromSerialPort(SerialPort serialPort)
{
if (!serialPort.IsOpen)
serialPort.Open();
return serialPort.ReadLine();
}
}
}
Refactor the code above by making an interface MyAppCode.ISerialPort
that declares the three members of System.IO.Ports.SerialPort
that we need.
namespace MyAppCode
{
public interface ISerialPort
{
bool IsOpen { get; }
void Open();
string ReadLine();
}
public static class MyClass
{
public static string ReadFromSerialPort(ISerialPort serialPort)
{
if (!serialPort.IsOpen)
serialPort.Open();
return serialPort.ReadLine();
}
}
}
Now time for the trick. We’ll make a new class MyAppCode.MySerialPort
that inherits from System.IO.Ports.SerialPort
and implements MyAppCode.ISerialPort
.
using System.IO.Ports;
namespace MyAppCode
{
public class MySerialPort : SerialPort, ISerialPort
{
}
}
That’s all we need! MyAppCode.MySerialPort
doesn’t need any code to implement MyAppCode.ISerialPort
because its base class already has members with the signature of each member of MyAppCode.ISerialPort
.
We can now make and use a simulated serial port like so:
using System;
namespace MyTestCode
{
public class SimSerialPort : MyAppCode.ISerialPort
{
public bool IsOpen { get; private set; } = false;
public void Open() => IsOpen = true;
public string ReadLine()
{
if (!IsOpen)
throw new InvalidOperationException();
return "Hello simulated world!";
}
}
static class TestMyClass
{
public static void TestReadFromSerialPort()
{
MyAppCode.MyClass.ReadFromSerialPort(new SimSerialPort());
}
}
}
Remember to /// <inheritdoc/>
There’s one last change to make. Since in our code we’ll now be referencing MyAppCode.ISerialPort
instead of System.IO.Ports.SerialPort
we’re not going to see all the great documentation we’re used to in Visual Studio when we mouse over members like Open()
and ReadLine()
. Not to worry, though, there’s a very simple fix. Just add /// <inheritdoc cref="SerialPort.X"/>
to each of ISerialPort
‘s members, where X
is the member name. Here’s our final interface:
using System.IO.Ports;
namespace MyAppCode
{
public interface ISerialPort
{
/// <inheritdoc cref="SerialPort.IsOpen"/>
bool IsOpen { get; }
/// <inheritdoc cref="SerialPort.Open"/>
void Open();
/// <inheritdoc cref="SerialPort.ReadLine"/>
string ReadLine();
}
}
And here’s a screenshot of how the documentation looks when we mouse over something:
Restricting a class
This trick mostly restricts a class to only those members that are declared in the interface that you create. This could be useful if the class contains members that you don’t want people to use for whatever reason. However, I say “mostly restricts” since the the full class can still be accessed via casting, like in the code below.
public static void Cast(ISerialPort serialPort)
{
var cast = serialPort as SerialPort;
// Close() is not a member of ISerialPort
cast!.Close();
}
Sealed classes
Obviously, this trick won’t work on a class marked sealed
since such a class cannot be inherited. However, you can still implement your interface through a sealed class by using composition like in the code below. The only downside is that you’ll probably get tired of writing all those lambdas.
using System.IO.Ports;
namespace MyAppCode
{
class CompositionSerialPort : ISerialPort
{
readonly SerialPort _serialPort = new();
public bool IsOpen => _serialPort.IsOpen;
public void Open() => _serialPort.Open();
public string ReadLine() => _serialPort.ReadLine();
}
}
Conclusion
I’ll admit, this trick is far from groundbreaking, but I still think it’s cool. When I first tried this and found that it actually compiled I had a big sense of “What? No way!” since I had thought that I would need to explicitly define all of ISerialPort
‘s members on MySerialPort
.
Source code for these examples is freely available here.