For a quick summary, if you really want to make this testable, I recommend:
- Extracting the data formatting
code into a new class (which
implements an interface you can use
to mock).
- Passing this class your
DataSet
.
- Make the new class return the
DataSet
as an IXmlSerializable
that you can also mock.
Without seeing the current code, I have to make some assumptions (hopefully not too many!) - so perhaps the current code looks something like this:
public class EmployeeService {
private IEmployeeRepository _Repository;
public EmployeeService(IRepository repository) {
this._Repository = repository;
}
public void ExportEmployeeData(int employeeId, string path) {
DataSet dataSet = this._Repository.Get(employeeId);
// ... Format data in the dataset here ...
dataSet.WriteXml(path);
}
}
This code is simple and effective, but not testable (without side-effects). Additionally, having all that logic in one place is a violation of the single-responsibility principal.
Depending on your needs, that's probably fine! Fulfilling the SRP is always just a goal, and we always need to balance testability with other factors that influence our designs.
However, if you want to segregate responsibilities a bit more and make it testable, you could do so by moving the formatting logic into its own class (which will implement IEmployeeDataSetFormatter
), then inject an IEmployeeDataSetFormatter
into this method call (alternately we could inject it into the service's constructor, just like the IEmployeeRepository
). The method that formats the data will return an IXmlSerializable
so we can mock it for safe, isolated testing:
public interface IEmployeeDataSetFormatter {
IXmlSerializable FormatForExport(DataSet dataSet);
}
public class EmployeeDataSetFormatter: IEmployeeDataSetFormatter {
public IXmlSerializable FormatForExport(DataSet dataSet) {
// ... Format data in the dataset here ...
return (IXmlSerializable) dataSet;
}
}
public void ExportEmployeeData2(int employeeId, string path, IEmployeeDataSetFormatter formatter) {
DataSet dataSet = this._Repository.Get(employeeId);
IXmlSerializable xmlSerializable = formatter.FormatForExport(dataSet);
// This is still an intermediary step - it's probably worth
// moving this logic into its own class so you don't have to deal
// with the concrete FileStream underlying the XmlWriter here
using (XmlWriter writer = XmlWriter.Create(path)) {
xmlSerializable.WriteXml(writer);
}
}
This has definite costs. It adds an additional interface, an additional class, and a bit more complexity. But it's testable and more modular (in a good way, I think).