Unit testing streams with FakeItEasy
Imagine you need to add a simple, new feature to your application - a class which logs changes made to entities and saves them in XML file. For example, when user edits his profile to change his first name, we expect this XML to be produced:
Simple? Let’s write a test.
Directly translating our requirement from above to unit test code will look more or less like this:
Exactly, now what? Assuming how tested class is supposed to work, we need to read some file in order to verify the XML. Wait one second… “read some file” in unit test? This is not how you produce high quality, maintainable code and fast running unit tests. It seems that implementation we thought about is not testable (as for now).
Our problem exists because of .NET framework API being excellent. I/O tasks usually take 2 lines of code once you find appropriate method(s). Generating XML is no different thanks to LINQ to XML:
Save method is our main offender. Under the hood it creates file in local directory and writes XElement content to it. This is a no-go if we want to have it unit tested.
While this might be cool feature it does not really get us anywhere in terms of testability. Not to mention, we might need a little bit more control over when and how files are created. The flexibility we are looking for is all there in Stream class:
Almost there. We need to remove that hard dependency to File otherwise we won’t be able to mock it in test with tools we got (FakeItEasy). This is usually done by wrapping I/O methods with class implementing custom interface and injecting such wrapper as a dependency. Final, fully-testable version of our class looks like this:
Good! Let’s get back to our test that we had problem writing in first place and change it accordingly:
Alright! The code is now 100% testable and we seem to be done. Or do we?
Unfortunately, this test fails with an error saying that we “Cannot access a closed Stream”. Upon closer inspection, the stream returned by IFileStreamFactory fake is used within using statement which disposes IDisposable (which in case of Stream closes it and renders unusable past using block). In order to save stream content for later inspection in unit test we have to somehow record it while it is being written. The only time-window we can access our stream is within using block.
Remember that IFileStreamFactory we introduced to make our class testable in first place? It returns Stream which is an abstract class. That means we could simply create a derived class, say RecordingStream, which would store whatever is written to stream for future retrieval.
Implementing 10-or-so Stream members seems rather excessive when all we need is to hook into
Write method. Fortunately, we can use FakeItEasy to cover both the necessary and unnecessary stuff.
1. Fake Stream instance
First, we need usable Stream. This is fairly simple:
2. Saving Stream content
Next, in order to get stream to store its content we need to configure
WriteByte methods (and their async versions if you are on .NET 4.5+). This can be done using FakeItEasy’s
Invokes method which allows us to execute custom code when call to faked method is made:
3. Extracting saved content
Having raw bytes in unit test is far from optimal as we will be rather making string-to-string asserts. We need to access those bytes in more friendly format, like string. To do this we will use Encoding.GetString()1 method:
Today, we have discovered two major obstacles when it comes to unit testing:
- Interaction with external resources (file, network, driver - you name it) is difficult to implement with testability in mind.
- .NET framework API does not always support writing unit tests.
By overcoming them we have laid down important foundation. From now on, anyone implementing any sort of file writing will have components needed to do it in testable manner (IFileStreamFactory) and easily test it (StreamRecorder) while keeping code up to the highest standards.
Sample code for this article can be found at my GitHub repository.
We use UTF8 because that is the default for .NET. This can naturally be configured in case we need to write in different encoding. ↩
There is still room for improvement though. Like I mentioned, 1) encoding could be configurable, 2) XMLs could be loaded from embedded resources rather than hand-typed in code, 3) asserts could be made for XElements rather than strings with FluentAssertions ↩