Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing a Class That Uses the File System

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.

like image 592
James Avatar asked Feb 11 '10 16:02

James


People also ask

Should unit tests use files?

For unit test I would suggest that you include the test file in your project(EAR file or equivalent) then use a relative path in the unit tests i.e. "../testdata/testfile". As long as your project is correctly exported/imported than your unit test should work.

Which testing is used for unit testing?

Unit testing uses all white box testing techniques as it uses the code of software application: Data flow Testing. Control Flow Testing. Branch Coverage Testing.

Can unit tests use database?

It is meant to make sure that definable modules of code work as expected. To test an application it is not enough to use unit tests. You must also perform functional testing and regression testing. Database access falls outside the scope of unit testing, so you would not write unit tests that include database access.

Is unit testing a system test?

In unit testing, independent software's module are tested separately. System testing is done to check whether the software or product meets the specified requirements or not.


2 Answers

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!
    }
}
like image 105
Jeff Sternal Avatar answered Nov 02 '22 14:11

Jeff Sternal


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.

like image 22
Jon Limjap Avatar answered Nov 02 '22 16:11

Jon Limjap