views:

187

answers:

2

I have a class that outputs a simple report file. It reads some record ID numbers from an XML file: each one used for finding a matching record stored in a database. It then writes out each record's details to a CSV file.

I am wondering - what is the best way to organise it so that it is easy to test, yet follows the principles of encapsulation? I gather that it is best to avoid interacting with the file system unless absolutely necessary so I am dealing with Stream objects. When unit testing I can use partial mock objects to override the bits that read or write to files.

I am also unsure of when/where to dispose the streams without making unit testing tricky. It looks like I might have to expose the streams to the unit test.

My project uses NHibernate for data access, Spring .NET for dependency injection, and Rhino.Mocks for unit testing.

Currently I have something similar to this :

public class ReportBiz
{
    //Access to repository, populated by Spring
    internal ICardRequestDAO CardRequestData { get;set;} 

    //This normally returns a FileStream containing data from the XML file. When testing this is overridden by using a Rhino.Mocks partial mock and returns a MemoryStream
    internal virtual Stream GetSourceStream()
    {
        //Load file and return a Stream
        ...
    }

    //This normally returns a FileStream where CSV data is saved. When testing this is overridden by using a Rhino.Mocks partial mock and returns a MemoryStream
    internal virtual Stream GetOutputStream()
    {
        //Create file where CSV data gets saved and return a Stream
        ...
    }

    public void CreateReportFile()
    {
        Stream sourceStream = GetSourceStream();
        ...

        //Create an XmlDocument instance using the stream
        //For each XML element, get the corresponding record from the database
        //Add record data to CSV stream     
        ...
    }
    }

Would it be better to use some kind of custom factory or something and pass the streams into the constructor? But what about if there is some business logic involved, e.g. the filename is determined based on results of a query?

Or is the whole file access thing not a problem after all?

Apologies if I'm missing something obvious. I would be grateful for any advice.

+5  A: 

The simplest way make the file access mock-able while retaining control over the lifecycle of the disposable resources is to inject a StreamFactory into your class:

public class ReportBiz {

    private IStreamFactory streamFactory;

    public ReportBiz(IStreamFactory streamFactory) {
        this.streamFactory = streamFactory
    }

    public void CreateReportFile() {
        using(Stream stream = this.streamFactory.CreateStream()) {
            // perform the important work!
        }
    }
}

When there's more business logic involved, your factory method may be a bit more elaborate, but not by much:

public void CreateReportFile() {
    string sourcePath   = this.otherComponent.GetReportPath();
    using(Stream stream = this.streamFactory.CreateStream(sourcePath)) {
        // perform the important work!
    }
}
Jeff Sternal
Thanks for the reply. But I am still a bit confused about how I then verify the CSV report file stream to make sure that I have written out the correct data.If the output stream is part of a using statement then I can't test it. Should I just hold a reference to it at class level and avoid disposing it until the ReportBiz instance itself gets disposed?
James
I haven't used Rhino Mocks, but in NMock, I'd inject a mock `IStreamFactory`, stub the call to its `CreateStream` method and make it return a mock Stream on which I would set expectations (for the calls to `Stream.Write`) to verify that I've written the correct data.
Jeff Sternal
A: 

You'll have to mock your streams somehow to avoid exposing them to the test, because the meat of your business logic is getting the input string and the output strings correctly.

The clincher in this scenario is you have to recognize that you have three stages in your data flow:

  • Reading the data: parsing the data is a unique, distinct concern
  • The content of the output: you have to verify that given correct data, you have the proper CSV string output
  • Writing that content to the file

Honestly, the whole file writing issue is not much of an issue -- the .NET Framework provides pretty well tested file writing functionalities, and it's beyond the scope of your testing. Most of the problems you will encounter will be the accuracy of the CSV that you spit out to the files.

As a precautionary note, I would advise against rolling your own CSV writer. You should try to find CSV libraries that already exist -- there are a lot of them over the net.

Jon Limjap