Simplify Unit Testing with IDisposable and the Using Statement

I’m sure I’m not the first to do this or something similar, but I’ll pat myself on the back all the same. Recently, I’ve been doing a lot of IoC and unit testing on some WCF services we’re working on. The services take implementations of our IRepository for data access which I’ve mocked out for testing purposes.

Most people are familiar with the using statement and it’s purpose to assure objects that implement IDisposable are properly cleaned up. A lot of people are also aware that you can use this interface and statement to do automatic scoping of some operations in a nice consistent manner. The first time I ever saw this type of pattern was as a shortcut to set the Cursor in a WinForms app to the WaitCursor and then back to the default once some long running operation was complete.

For Example:

using (CursorScope.SetCursor(this, Cursors.WaitCursor))
{
    // long running operation
}

Where the CursorScope class looks like this:

class CursorScope : IDisposable
{
    private Cursor _originalCursor;
    private Control _control;

    private CursorScope(Control control, Cursor newCursor)
    {
        _control = control;
        _originalCursor = _control.Cursor;
        _control.Cursor = newCursor;
    }

    public static CursorScope SetCursor(Control control, Cursor newCursor)
    {
        return new CursorScope(control, newCursor);
    }

    public void Dispose()
    {
        _control.Cursor = _originalCursor;   
    }
}

So as we enter the using statement, the cursor will be changed for the control, and as we leave the using statement and our object is “disposed” it will be set back to the original value regardless of any exceptions or errors that may occur between. This syntax and behavior is useful in a number situations. The most recent place I’ve been using this, as the title mentions is in some of my unit tests. To test out my services, I’ve built some mock repositories for the services to call. There are two scenarios where this has come in handy.

The first is when I’m trying to test the error and exception handling in my service. To get complete code coverage you need to make sure your tests also throw exceptions where you’re expecting them to be handled otherwise your catch block will never be tested. To support this, I added a boolean flag on my mock repository called ThrowException, when this is set to true, any operation you try to take on the repository with throw an exception I could use this in one of two ways, I could try to remember always setting the flag to true and then back to false when I’m done testing it. The problem there is depending on how you’re unit tests are run and set up, if you forget to set the flag back to false, it may cause your mock repository to throw exceptions in other unit tests. To help avoid this situation I made the flag private and added a ThrowException() method to the mock repository that returns an IDisposable.

private bool AllMethodsThrowExceptions { get; set; }

public IDisposable ThrowExceptions()
{
    AllMethodsThrowExceptions = true;
    return new ExceptionDisposable(this);
}

class ExceptionDisposable : IDisposable
{
    public MockRepository Repository { get; set; }
    public ExceptionDisposable(MockRepository repo)
    {
        Repository = repo;
    }

    public void Dispose()
    {
        Repository.AllMethodsThrowExceptions = false;    
    }
}

And then in our unit test we can test the exception path like so:

[TestMethod]
public void GetUserExceptionTest()
{
    using (_mockUserRepo.ThrowExceptions())
    {
        _log.ClearLog();
        var response = _service.GetUser(new GetUserRequest());
        Assert.IsNull(response.User);
        Assert.AreEqual(1, response.ErrorMessages.Count);
    }
}

Which will cause our exceptions to be thrown so we can test to make sure our code handles them properly. Then, when we’re done with that part of our unit testing, it will reset the flag back so any other tests that run subsequently, everything will work correctly.

The other area where this comes in useful is because our mock repository uses in-memory data, and as we manipulate this could throw off our tests as well, so we implement the same pattern, but this time instead of setting a flag, we copy our repository’s data and reset it once the test is complete.

public IDisposable ChangingData()
{
    return new ChangingDataDisposable(this);
}

public class ChangingDataDisposable : IDisposable
{
    public MockRepository<TEntity> Repository { get; set; }
    private List<TEntity> OriginalData;
    public ChangingDataDisposable(MockRepository<TEntity> repo)
    {
        Repository = repo;
        OriginalData = CloneData(Repository.Data);
    }

    private T CloneData<T>(T data)
    {
        if (!typeof(T).IsSerializable)
            throw new ArgumentException("Must be serializable.", "data");

        if (Object.ReferenceEquals(data, null))
            return default(T);

        var s = new DataContractSerializer(typeof(T));
        using (var stream = new MemoryStream())
        {
            s.WriteObject(stream, data);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)s.ReadObject(stream) ;
        }
    }

    public void Dispose()
    {
        Repository.Data = OriginalData;
    }
}

Here when you call ChangingData() I clone the data using serialization, and again, when you’ve disposed the object it resets the data.

[TestMethod]
public void UpdateUserTest()
{
    using (_mockUserRepo.ChangingData()) 
    {
        var user = _mockUserRepo.Data.FirstOrDefault(i => i.Id == 1);
        Assert.AreEqual("Nick", user.FirstName);

        user.FirstName = "Brian";

        var request = new UpdateUserRequest();
        request.User = user;

        var response = _service.UpdateUser(request);
        Assert.AreEqual("Brian", user.FirstName);
        Assert.AreEqual(1, response.EntityId);
    }
}

And all the data is reset back to the original values and ready for the next test to work off it.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>