Sunday, March 16, 2008

Injectable file adapters

Someone must have done this, but I really couldn't find it. I'm talking about an IoC-friendly System.IO.File replacement. You know, unit tests shouldn't touch the file system, etc. So if your code writes a file and want to unit-test it, you pretty much have to mock the writing of the file. Except there's a known problem, System.IO.File is a static class, and those are not mockable. Even TypeMock can't mock System.IO.File since it's part of mscorlib. So the only solution left is to build an interface and a wrapper around File. So, here's the source, probably the most boring code I've ever written. I've also included a static locator that gets the IFile implementation using Ayende's IoC static accessor to the Windsor Container, so you can write code like this:

[Test]
public void Copy() {
    var mocks = new MockRepository();
    var container = mocks.CreateMock<IWindsorContainer>();
    var fileImpl = mocks.CreateMock<IFile>();
    IoC.Initialize(container);
    With.Mocks(mocks).Expecting(delegate {
        SetupResult.For(container.Resolve<IFile>()).Return(fileImpl);
        Expect.Call(() => fileImpl.Copy(null, null))
            .IgnoreArguments()
            .Repeat.Once();
    }).Verify(delegate {
        FileEx.Copy("source", "dest");                
    });
}

So you only have to replace your calls to System.IO.File to FileEx and that's it. You get the benefits of testability and extensibility (yes, sometimes you need to provide a different behavior for File.WriteAllText()) and you don't have to deal with interfaces, implementations, etc once you have set up the container. Personally, I prefer to make explicit the dependency for IFile in my components.

Like I said, I was very surprised that I couldn't find a working implementation of this... specially because there was a big debate a year ago about maintenability, YAGNI, dependency injection, coupling and more, and Anders NorĂ¥s commented this solution on one of his own posts.

Well, I hope someone finds this useful.

8 comments:

RedGreenRefactor said...

Nice, also check jayflowers doubler.

David Keaveny said...

http://systemwrapper.codeplex.com does a pretty comprehensive job of wrapping most of System.IO and quite a few of the other System namespaces.

mausch said...

@David Keaveny: indeed, now there are a couple more that do this, but take a look at the dates.

Anonymous said...

I still like the simplicity of what you have written after reviewing my options. Thanks again for sharing.

~BuckG

Stefan said...

FYI, the last version of JustMock can mock every method in mscorlib.

~K said...

I've pushed this into nuget as mscorlib-mock (https://nuget.org/packages/mscorlib-mock/)

Mauricio Scheffer said...

@Kori : nice!

Gavin S said...

~K Why the Castle Windsor dependency ?