jimmy keen

on .NET, C# and unit testing

Unit testing streams with FakeItEasy

December 21, 2014 | tags: c# unit-testing fakeiteasy tdd

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:

<Changes type="User">
  <FirstName>Bobby</FirstName>
</Changes>

Simple? Let’s write a test.

Enter TDD

Directly translating our requirement from above to unit test code will look more or less like this:

[Test]
public void LogChanges_SavesChangedFirstNameToXml()
{
    const string ExpectedXml = 
      "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + 
      "<Changes type=\"User\"><FirstName>Bobby</FirstName></Changes>";
    var userChangeLogger = new UserChangeLogger();
    var oldUser = new User { FirstName = "John" };
    var newUser = new User { FirstName = "Bobby" };
    
    userChangeLogger.LogChanges(oldUser, newUser);
    
    // now what?
}

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).

Naive implementation

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:

public void LogChanges(User originalUser, User changedUser)
{
    if (originalUser.FirstName != changedUser.FirstName)
    {
        var userChangesXml = new XElement("Changes",
            new XElement("FirstName", changedUser.FirstName));
        userChangesXml.Save(@"UserChanges.xml");
    }
}

The 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.

Utilizing System.IO.Stream

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:

using (var fileStream = File.Open(@"UserChanges.xml", FileMode.Create))
{
    var userChangesXml = new XElement("Changes",
        new XElement("FirstName", changedUser.FirstName));
    userChangesXml.Save(fileStream);
}

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:

class UserChangeLogger
{
    public UserChangeLogger(IFileStreamFactory fileStreamFactory)
    {
        this.fileStreamFactory = fileStreamFactory;
    }

    public void LogChanges(User originalUser, User changedUser)
    {
        if (originalUser.FirstName != changedUser.FirstName)
        {
            using (var stream = fileStreamFactory
                .CreateFileStream(@"UserChanges.xml"))
            using (var writer = XmlWriter.Create(stream))
            {
                var userChangesXml = new XElement("Changes",
                    new XElement("FirstName", changedUser.FirstName));
                userChangesXml.WriteTo(writer);
            }
        }
    }

    private IFileStreamFactory fileStreamFactory;
}

Good! Let’s get back to our test that we had problem writing in first place and change it accordingly:

[Test]
public void LogChanges_SavesChangedFirstNameToXml()
{
    const string ExpectedXml = 
      "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + 
      "<Changes type=\"User\"><FirstName>Bobby</FirstName></Changes>";
    // we set up our new dependency and "tell" it to return stream
    // so that later we can check what have been written to it
    var fileStreamFactory = A.Fake<IFileStreamFactory>();
    var memoryStream = new MemoryStream();
    A.CallTo(() => fileStreamFactory.CreateFileStream(@"UserChanges.xml"))
        .Returns(memoryStream);
    var userChangeLogger = new UserChangeLogger(fileStreamFactory);
    var originalUser = new User { FirstName = "John" };
    var changedUser = new User { FirstName = "Bobby" };

    userChangeLogger.LogChanges(originalUser, changedUser);

    memoryStream.Position = 0;
    var savedXml = new StreamReader(memoryStream).ReadToEnd();
    Assert.That(savedXml, Is.EqualTo(ExpectedXml);
}

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.

Enter FakeItEasy

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:

var fakeStream = A.Fake<Stream>();
A.CallTo(() => fakeStream.CanWrite).Returns(true);
A.CallTo(() => fakeStream.CanRead).Returns(true);

2. Saving Stream content

Next, in order to get stream to store its content we need to configure Write and 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:

// The _ is FakeItEasy's shortcut to match-any
A.CallTo(() => fakeStream.Write(A<byte[]>._, A<int>._, A<int>._))
    .Invokes(call =>
    {
        // call variable stores original method invocation details
        // including arguments which is all we need here
        var offset = call.GetArgument<int>(1);
        var count = call.GetArgument<int>(2);
        var content = call.GetArgument<byte[]>(0)
            .Skip(offset)
            .Take(count)
            .ToArray();
        // save content
        bytesWritten.AddRange(content);
    });
// Similar for WriteByte method

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:

return new UTF8Encoding().GetString(bytesWritten.ToArray());

We wrap it all up and put inside a custom class, StreamRecorder (follow the link to view its final form on my GitHub repository). With few modifications to unit test code we are now officially done.

Conclusion

Today, we have discovered two major obstacles when it comes to unit testing:

  1. Interaction with external resources (file, network, driver - you name it) is difficult to implement with testability in mind.
  2. .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.

  1. We use UTF8 because that is the default for .NET. This can naturally be configured in case we need to write in different encoding.

Mocking frameworks comparison

December 13, 2014 | tags: unit-testing mocking c#

You might have been redirected to this page.

Out with the old, in with the new

While ago I used to run this post series called “Mocking frameworks comparison” where I was comparing API of RhinoMocks, Moq and FakeItEasy. Long story short, it is no more.

Why have I decided to bury the series? I think we should be using modern frameworks, like FakeItEasy, Moq or NSubstitute. Comparing them with each other makes little sense as they are nearly identical both functionality-wise and syntax-wise (on top of that they all have excellent documentation and active community). Comparing them with RhinoMocks makes little sense as Rhino is old and no longer developed framework1 with often confusing syntax spanning across different version2.

Which framework to use?

The question remains. Rather than giving you straight answer I will answer a different question instead. Which framework you should stay away from? - RhinoMocks. Why?

  1. Its API is old, unpleasant and undocumented. It will make learning (it) harder and your tests will be more difficult to follow.
  2. It is no longer actively developed (regardless of recent attempts).
  3. It is one-man show (who might get bored and leave at some point).
  4. It is in alpha phase (since spring this year).

You do not want such library in your project. Rhino was excellent library but that was 5 years ago. Nowadays, there are simply better kids on the block (I am thinking FakeItEasy, Moq and NSubstitute). Use either of them. As for me, I stick with FakeItEasy as it is the exact opposite of Rhino’s don’ts3.


  1. I am aware that there has been recent activity and an attempt to resurrect Rhino but after initial excitement it does not seem to be that continuous anymore.

  2. Record-Replay users, anyone?

  3. Simply check FakeItEasy project page on GitHub (including recent activity) and you will see what I mean.

How to migrate from SVN to Git on Windows using Tortoise clients

November 03, 2012 | tags: version-control git svn

Problem

My setup is simple. I have external HDD hooked, where I got my local repositories (for both Git and SVN). Being local file storage, all the navigation/URIs is done with file:/// prefixing. For version control, I usually use both TortoiseGit and TortoiseSVN clients, but now, I wanted to move one of my project from SVN repository to Git repository. It all seemed simple:

  • have the original svn repository for Project located at F:\SVN_Repository\Project
  • targeted git repository for Project should be at F:\GIT_Repository\Project
  • use git clone from SVN repo command to preserve commits history

Unfortunatelly, TortoiseGit had other plans and displayed such error message:

Couldn’t open a repository: Unable to open an ra_local session to URL: Unable to open repository ‘file:///F:/SVN_Repository/Project/trunk’: Expected FS format ‘2’; found format ‘4’ at …

Solution

After some quick googling, it turned out to be well-known issue with TortoiseGit, and solution was apparently simple - all you have to do is serve SVN repository to Git via svnserve service:

  1. First, you need to get and install1 svnserve.exe. I wasn’t able to find it anywhere on my box, so I figured it doesn’t come with typical TortoiseSVN installation.
  2. Turns out it (svnserve.exe) comes with Subversion. I’ve grabbed Subversion installer from SlikSVN. You can get whichever you like, just make sure it comes with svnserve.exe (as this is our main star in this show).
  3. When installing, double check that SVNServe is actually going to be installed (for example, typical install of SlikSVN I used didn’t have it, so I had to manually select it in custom install).
  4. Once installation completes, navigate to installation directory and start svnserve.exe process, pointing it to your local SVN repository:

    cd "C:\Program Files\SlikSvn\bin"
    svnserve.exe --daemon --root F:\SVN_Repository

  5. To verify whether svnserve hooked up to your repository correctly, open Repo-browser for project you are migrating. The URL should use old format, with file:///SVN_Repository prefix - you need to replace it with svn://localhost/:

    old:
    file:///SVN_Repository/Project
    new:
    svn://localhost/Project

  6. If repo browser window correctly displays your project structure, it’s all good.
  7. Now, all you need to do is use git clone which failed at the very beginning (this will take a while as Git is going to rewrite entire commits history into its own format):

git clone

At this point it is job done. However, I didn’t really like that Git was hooked to SVN (context menu has extra svn-related commands, which I didn’t plan to use) and all that weirdness, so I decided to push entire project into brand new Git repository and clone that to my working directory:

  1. Create bare repo for your project in external storage directory (F:\GIT_Repository\Project).
  2. Point your SVN-migrated, now Git project’s origin to that repository (put bare repo path into URL box of TortoiseGit -> Push -> (Destination) Manage window).
  3. Push master to your new repository (this will naturally push entire commits history).
  4. git clone project from Git repository to your local workspace.

SVN has left the building, and the only reminiscence of it is git-svn-id tag in your commits history. For the sake of completeness, rebuild your project and run unit tests :-)


  1. There was a tiny tutorial at TortoiseSVN website but it seems to be gone. Follow guide from SilkSVN website instead.