Reputation: 23373
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
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
Reputation: 25676
If this were IoC you'd be passing in the CsvParser and not the path.
Upvotes: 3
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
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