Generics make many coding tasks easier but do introduce subtle unit testing challenges. Consider the following code examples:
public class Contoso<T>
where T : IWoodgrove, new()
{
public Contoso() {}
public void MakeMagic()
{
T t;
t = new T();
// use t...
}
}
public class Fabrikam<T>
where T : IWoodgrove
{
public Fabrikam(T t)
{
this.t = t;
}
private readonly T t;
public void MakeMagic()
{
// use this.t...
}
}
The Contoso`1 class uses an instance of type T many times and class Fabrikam`1 uses a single instance of type T for its entire lifetime. These two designs affect how one would go about unit testing for incorrectness and correctness. If you truly wanted to test the interaction between Contoso`1/T and Fabrikam`1/T, then you would introduce mock objects into your unit test via some type mocking library (NMock2, RhinoMocks, etc.). The problem is that you cannot dynamically mock a generic parameter since it is static in nature.
The solution for a Contoso`1 style of generic class is to create a non-generic super class leveraging a factory interface parameter.
Consider:
public interface ITeeFactory
{
IWoodgrove Create();
}
public class Contoso
{
public Contoso(ITeeFactory teeFactory)
{
this.teeFactory = teeFactory;
}
private ITeeFactory teeFactory;
public void MakeMagic()
{
IWoodgrove t;
t = this.teeFactory.Create();
// use t...
}
}
public class GenericTeeFactory<T> : ITeeFactory
where T : IWoodgrove
{
public IWoodgrove Create()
{
return new T();
}
}
public class Contoso<T>
where T : IWoodgrove, new()
{
public Contoso() : base(new GenericTeeFactory<T>()) {}
}
The solution for a Fabrikam`1 style of generic class is to create a non-generic super class leveraging a typical dependency injection model.
Consider:
public class Fabrikam
{
public Fabrikam(IWoodgrove t)
{
this.t = t;
}
private readonly IWoodgrove t;
public void MakeMagic()
{
// use this.t...
}
}
public class Fabrikam<T> : Fabrikam
where T : IWoodgrove
{
public Fabrikam(T t) : base(t)
{
}
}
With these two solutions in hand, mocking these styles of classes becomes easy.
(NOTE: Stream of thought coding in examples above. Not tested to ensure compilation.)