Consider this simplistic example, which uses a stream reader.
public class FileLoader { private readonly StreamReader _streamReader; public FileLoader(StreamReader streamReader) { _streamReader = streamReader; } public IEnumerable<string> Read() { var output = new List<string>(); string line; while (!string.IsNullOrEmpty(line = _streamReader.ReadLine())) { output.Add(line); } _streamReader.Close(); _streamReader.Dispose(); return output; } }
The problem I have with this class is that it isn’t exactly unit testable. First of all, it uses a stream reader and it’s not straightforward to be able to mock a stream reader due to the lack of an interface. Secondly what I reckon most developers would do is pass a real stream reader into the class and write tests in that fashion, which would be more of an integration test.
Personally that is not proper unit testing as the class dependencies are not mocked. It is somewhat unavoidable we would be using un-mockable classes from the .NET framework, such as ConfigurationManager, Environment.GetVariable, etc.
The way I would solve this problem is create an interface for StreamReader, so I can mock it. As for the concrete implementation, I would create a wrapper class for StreamReader which implements that interface. So that makes my class unit testable like so.
public interface IStreamReader { string ReadLine(); void Close(); void Dispose(); } public class FileLoader { private readonly IStreamReader _streamReader; public FileLoader(IStreamReader streamReader) { _streamReader = streamReader; } public IEnumerable<string> Read() { var output = new List<string>(); string line; while (!string.IsNullOrEmpty(line = _streamReader.ReadLine())) { output.Add(line); } _streamReader.Close(); _streamReader.Dispose(); return output; } }
Is this too extreme? I would actually write wrappers for many un-mockable classes so I can unit test them. This brings me another unfortunate side-effect, which is I end up writing lots of wrapper classes. This is not ideal, so I came up with an ObjectProxy which could create a proxy object that implements an interface I specify and delegate interface calls to the real object. Referencing the StreamReader example, I would create an IStreamReader like so.
public IEnumerable<string> ReadFile() { var streamReader = new StreamReader(@"C:\somefile.txt"); var fileLoader = new FileLoader(ObjectProxy<IStreamReader>.Create(streamReader)); return fileLoader.Read(); }
There are other ways to achieve the same result. For instance, I believe you could use Castle DynamicProxy libaries, or using .NET Reflection Emit. My approach is just simply another method, and one I prefer over the others. Here’s the ObjectProxy.
using System; using System.Reflection; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Proxies; namespace EdLib.Objects.Proxy { public class ObjectProxy<TInterface> : RealProxy where TInterface : class { private readonly object _instance; private ObjectProxy(object instance) : base(typeof(TInterface)) { _instance = instance; } public static TInterface Create(object instance) { return (TInterface)new ObjectProxy<TInterface>(instance).GetTransparentProxy(); } public override IMessage Invoke(IMessage msg) { var methodCall = (IMethodCallMessage)msg; var method = (MethodInfo)methodCall.MethodBase; try { var returnValue = TryInvokeMethodFromRealInstance(methodCall, method); return new ReturnMessage(returnValue, null, 0, methodCall.LogicalCallContext, methodCall); } catch (Exception ex) { if (ex is TargetInvocationException && ex.InnerException != null) { return new ReturnMessage(ex.InnerException, methodCall); } return new ReturnMessage(ex, methodCall); } } private object TryInvokeMethodFromRealInstance(IMethodCallMessage methodCall, MethodInfo methodInfo) { var instanceMethodInfo = _instance.GetType().GetMethod(methodInfo.Name); if (instanceMethodInfo != null) { return instanceMethodInfo.Invoke(_instance, methodCall.InArgs); } throw new Exception(string.Format("unable to find method {0} from real instance", methodInfo.Name)); } } }
This can also be from in EdLib @ Github, my code library.
