Josh Close
Josh Close

Reputation: 23373

Creation Of Object That Uses Inversion Of Control

I'm creating a CSV reader (yes I know about Fast CSV Reader and FileHelpers). The CsvReader class uses the CsvParser class to parse the CSV file. I want to make the CsvReader class unit testable, so I want to be able to externally set the CsvParser class that is used (also, so you can create your own implementation). I also, don't want to have to create a parser and pass it in on normal use.

This is how I would like to use it.

var reader = new CsvReader( "path/to/file.csv" );

When doing this, I could create the CsvParser in the constructor of the CsvReader and have a property to change the parser.

public ICsvParser Parser { get; set; }

public CsvReader( filePath )
{
    Parser = new CsvParser( filepath );
}

But then when unit testing, the default parser is always created and I only want to test the CsvReader.

The parser could be passed into the constructor, but I don't want to have to create a parser separately on normal use. This seems like a good place for a factory.

This seems like it would be a common problem when using IOC. What is a good solution for this?

Upvotes: 2

Views: 531

Answers (4)

Doc Brown
Doc Brown

Reputation: 20044

Create two constructors, one with the parser interface as a parameter, and one just with filePath. Let the the second one create CsvParser and let it call the first one with this object. Your test code then can use the first constructor and pass a mock CsvParser.

This solution has one drawback: the assembly containing CsvReader has to reference the one containing CsvParser. You have to decide by yourself if this ok or not in your situation.

Upvotes: 0

chillitom
chillitom

Reputation: 25676

If this were IoC you'd be passing in the CsvParser and not the path.

Upvotes: 3

Paolo
Paolo

Reputation: 22638

Typically you would have the parser passed in via the constructor and create your object using the IoC container, this would then inject a parser instance into the constructor for you.

In unit testing you would pass in a mock parser either directly, e.g. new CsvReader(new MockParser()) or by configuring your IoC with a test configuration that injected the mock for you.

Upvotes: 0

jason
jason

Reputation: 241583

The solution is to rewrite your constructor of CsvReader to accept an implementation of ICsvParser and your concrete implementation of ICsvParser should have a constructor taking in its dependencies (a path to a file to parse) and an already constructed ICsvParser should be injected into the constructor for CsvReader:

public CsvReader(ICsvParser parser) {
    this.Parser = parser;
}

The ICsvParser should already be constructed to accept its dependency (a path to the file to be parsed).

Thusly:

// path is string containing path to file to parse
ICsvParser parser = new SomeCsvParser(path);
ICsvReader reader = new CsvReader(parser);

The point is that CsvReader does not need a path, it just needs a CsvParser. Further, CsvReader should not need to be aware of the dependencies of CsvParser (that it needs a path to a file to parse) lest it becomes dependent on those dependencies too.

new inside of constructors is a smell.

Upvotes: 5

Related Questions