Unit testing against a rigid, closed design

I mentioned in a previous post about how one might unit test a class that uses MessageBox.Show – a UI element that requires interaction. We discussed using a delegate to “swap out” the implementation after-the-fact.

Ideally, if functionality is written according to interfaces or at least base-classes, then to create mock functionality – you can use straight-forward techniques. However, when working with the .NET Framework for example, most types are not defined that way. In fact, many things are defined as sealed or static classes. when you need to unit test, these hard-coded, external dependencies can become a real problem.

I wanted to write this down in one place. This is the technique I’ve been using, and it seems to work quite well. The MessageBox example from the other day wasn’t ideal because I needed to publicly-expose the delegate. This isn’t bad necessarily, but it clutters up and confuses the externally-facing API. Given that the class needed to be static though, there was not another way. If your class is not static, here is an example of a slightly more elegant way to create mocks that have low overhead, do not muck up the API, and that require no external dependencies on mock frameworks.

Consider you have this functionality:

public class FileProcessor
{
public void WriteToFile(Uri path, Byte[] contents)
{
// ...other logic is here...

File.WriteAllBytes(path.LocalPath, contents);
}
}

Let’s try to write a unit test for it:

[TestMethod]
public void WriteToFileWithValidArguments()
{
// ARRANGE
FileProcessor processor = new FileProcessor();
Uri path = new Uri(@"C:testfile.txt");
Byte[] contents = new Byte[] { 0, 1, 2, 3 };

// ACT
processor.WriteToFile(path, contents);

// ASSERT
// TODO: What do I assert here? Nothing to test!
}

WriteToFile returns a void. So, not only can I not test a return value, I HAVE to interact with the file system too. That means I need to make sure that path exists and that there isn’t a file there already, etc. This is not ideal at all. I want to test this WriteToFile method, but I want to be able to get rid of the external dependencies.

All About The Delegate:
Now, the “delegate” is a mysterious thing to most and borderline confusing to the rest of us. However, if we can get a grasp on it, a delegate is what can actually give us a pretty elegant solution to this problem.

A delegate can be a few different things. For our purposes though, it will:

A) Define the “signature” of a method (return type, number and types of arguments)
B) Give us a function “pointer” that we can swap out, depending on need

Let’s address part A first. Let’s create a delegate:

protected delegate void FileWriteAllBytes(String path, Byte[] contents);

This is the “signature” that System.IO.File.WriteAllBytes(..) uses. In other words, this has the same return type and arguments as that WriteAllBytes method. Why is that useful? Hang on, first – let’s create a property that is going to be our “function pointer” (I’ll explain this in a minute):

public class FileProcessor
{
protected delegate void FileWriteAllBytes(String path, Byte[] contents);

public FileWriteAllBytes CurrentFileWriteAllBytes
{
get { return currentFileWriteAllBytes; }
set { currentFileWriteAllBytes = value; }
} private FileWriteAllBytes currentFileWriteAllBytes = File.WriteAllBytes;


public void WriteToFile(Uri path, Byte[] contents)
{
// ...other logic is here...

File.WriteAllBytes(path.LocalPath, contents);
}
}

Before we talk about what this can do, let’s talk about what it is. CurrentFileWriteAllBytes is a property, that points to a “delegate”. That means that instead of storing data (e.g. a variable) in this property, we are storing either null or a pointer to a function. We are storing the memory address of what code should be run. Can we point to any old function? No, the function MUST have the same signature as the specified delegate. This might become a bit clearer in a minute.
 
Meanwhile, the underlying private field (currentFileWriteAllBytes) is initialized, and defaulted to “point” to the method File.WriteAllBytes. Since that method has EXACTLY the same signature as this delegate, the compiler is fine with it. That property then, points to System.IO.File.WriteAllBytes(string, byte[]).
 
So far, we’ve declared a delegate, or a signature of what a method should look like – and mirrored it from the File.WriteAllBytes method. We have a property that holds one of these pointers – but we default it to point to File.WriteAllBytes.
 
Confused? How about this – note that the two last lines of code do exactly the same thing:
 
public class FileProcessor
{
protected delegate void FileWriteAllBytes(String path, Byte[] contents);

public FileWriteAllBytes CurrentFileWriteAllBytes
{
get { return currentFileWriteAllBytes; }
set { currentFileWriteAllBytes = value; }
} private FileWriteAllBytes currentFileWriteAllBytes = File.WriteAllBytes;

public void WriteToFile(Uri path, Byte[] contents)
{
// ...other logic is here...

// Calls File.WriteAllBytes directly...
File.WriteAllBytes(path.LocalPath, contents);

// Uses the delegate (function pointer) to invoke the code
// It is defaulted to point to File.WriteAllBytes
CurrentFileWriteAllBytes(path.LocalPath.Contains);
}
}

Hopefully that helps a little. We still are writing to the file system, but instead of hard-coding using File.WriteAllBytes, we’ve injected an intermediary. We put a function pointer, a delegate, in between that we can swap out if we want to. Again, by default, it points to the real File.WriteAllBytes, but it now opens the door for us to override it.

If this is still unclear, let’s create a mock of this class:

public class MockFileProcessor : FileProcessor
{
public MockFileProcessor()
: base()
{
}
}

If we did that and modified our unit test:

[TestMethod]
public void WriteToFileWithValidArguments()
{
// ARRANGE
MockFileProcessor processor = new MockFileProcessor();
Uri path = new Uri(@"C:testfile.txt");
Byte[] contents = new Byte[] { 0, 1, 2, 3 };

// ACT
processor.WriteToFile(path, contents);

// ASSERT
// TODO: What do I assert here? Nothing to test!
}

We’d be in the same boat. This is functionally equivalent to what we started with. However, we have a feature that we can take advantage of. Because our internal functionality now uses a delegate to write to the file system, we can override that with our own functionality.

Instead of “writing to the file system”, let’s have this “fire an event” that we can capture instead, and not even touch the file system. Let’s modify the MockFileProcessor class:

public class MockFileProcessor : FileProcessor
{
public event EventHandler FileWritten;

public MockFileProcessor()
: base()
{
this.CurrentFileWriteAllBytes = MockFileWriteAllBytes;
}

protected void MockFileWriteAllBytes(String path, Byte[] contents)
{
// Fire the event!
if (FileWritten != null)
FileWritten(this, new EventArgs());
}
}

In the constructor, we override the default “function pointer” that was pointing to System.IO.File.WriteAllBytes(..) and instead point it to our local MockFileWriteAllBytes(..). Polymorphism ensures that FileProcess will use our implementation, not the File.WriteAllBytes.

In our mock implementation, we are going to fire an event. How can that be used to our advantage? Let’s refactor that unit test, now that we have access to more notification:

[TestMethod]
public void WriteToFileWithValidArguments()
{
// ARRANGE
MockFileProcessor processor = new MockFileProcessor();
Uri path = new Uri(@"C:testfile.txt");
Byte[] contents = new Byte[] { 0, 1, 2, 3 };
Boolean fileWasWritten = false;

processor.FileWritten += new EventHandler((sender, e) =>
{
fileWasWritten = true;
});

// ACT
processor.WriteToFile(path, contents);

// ASSERT
Assert.IsTrue(fileWasWritten);
}

This too might be a little confusing, but hopefully it ultimately makes sense. What we are doing is this:

  • Create an instance of MockFileProcessor
  • Default a boolean flag called fileWasWritten to false
  • Wire up an anonymous delegate to the FileWritten event of the MockFileProcessor. When this event is fired, it will flip the fileWasWritten flag to true.
  • We call the WriteToFile method (which is actually going to run the real code in FileProcessor, but use OUR function pointer to write to the file system via the MockFileProcess, instead of the default File.WriteAllBytes).
  • We verify that the fileWasWritten flag was flipped during the operation.

To take this one step farther, I can also reduce my mock class too. Instead of breaking out my delegate functionality in it’s own method:

public class MockFileProcessor : FileProcessor
{
public event EventHandler FileWritten;

public MockFileProcessor()
: base()
{
this.CurrentFileWriteAllBytes = MockFileWriteAllBytes;
}

protected void MockFileWriteAllBytes(String path, Byte[] contents)
{
// Fire the event!
if (FileWritten != null)
FileWritten(this, new EventArgs());
}
}

I can just put that functionality in an anonymous delegate:

public class MockFileProcessor : FileProcessor
{
public event EventHandler FileWritten;

public MockFileProcessor()
: base()
{
this.CurrentFileWriteAllBytes = new FileWriteAllBytes((path, bytes) =>
{
// Fire the event!
if (FileWritten != null)
FileWritten(this, new EventArgs());
});
}
}

The idea here is that as you are writing components, you can expose delegates via protected members to make mocking and unit testing very simple to do. Again, it would be ideal to work in interfaces and base classes, but many times the underlying functionality simply doesn’t support when that is the case, this pattern can work pretty well. This allows you to make a “pure” unit test, allows you to test otherwise untestable code, and doesn’t mussy up your API in the process.

Posted in .NET 3.5, .NET 4.0, ASP.NET, ASP.NET MVC, Best-practices, DI and IOC, NETMF, Professional Development, Uncategorized, Unit Testing, WinForms, WPF and MVVM

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
Categories

Enter your email address to follow this blog and receive notifications of new posts by email.

Join 5 other followers

%d bloggers like this: