If you want to test-drive a file upload action, you will run into the age-old problem of System.Web's sealed classes - specifically HttpPostedFile. One solution I am using successfully is to utilise MRs excellent IParameterBinder interface to allow inject a "seam" into the fabric of the request pipeline, which you can then open up during testing.
Less talk, more action. Let's create an interface to HttpPostedFile called IHttpPostedFileAdapter and then create a class implementing it called HttpPostedFileAdapter:
public class HttpPostedFileAdapter : IHttpPostedFileAdapter { private HttpPostedFile httpPostedFile; public HttpPostedFileAdapter(HttpPostedFile httpPostedFile) { this.httpPostedFile = httpPostedFile; } public void SaveAs(string fileName) { httpPostedFile.SaveAs(fileName); } public int ContentLength { get { return httpPostedFile.ContentLength; } } public string ContentType { get { return httpPostedFile.ContentType; } } public string FileName { get { return httpPostedFile.FileName; } } public Stream InputStream { get { return httpPostedFile.InputStream; } } }
This wraps a standard HttpPostedFile with an interface which we now control.
Next, lets utilise the IParameterBinder interface to create an attribute which we can use within our action signature:
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] public class HttpPostedFileAdapterBinderAttribute : Attribute, IParameterBinder { private String filesKey; /// <summary> /// The files entry to use. If none /// is provided, the target parameter name is used /// </summary> public String FilesKey { get { return filesKey; } set { filesKey = value; } } public object Bind(SmartDispatcherController controller, ParameterInfo parameterInfo) { String key = ObtainKey(parameterInfo); HttpPostedFile httpPostedFile = controller.Context.Request.Files[key] as HttpPostedFile; return httpPostedFile == null ? null : new HttpPostedFileAdapter(httpPostedFile); } public int CalculateParamPoints(SmartDispatcherController controller, ParameterInfo parameterInfo) { String key = ObtainKey(parameterInfo); return controller.Context.Request.Files[key] != null ? 10 : 0; } private String ObtainKey(ParameterInfo parameterInfo) { return filesKey != null ? filesKey : parameterInfo.Name; } }
Now, the upload action method signature can evolve from:
public void ProcessUpload(HttpPostedFile file) { ... }
to
public void ProcessUpload([HttpPostedFileAdapterBinder] IHttpPostedFileAdapter file) { ... }
Meaning we can now mock the upload easily, using say Rhino Mocks or a concrete mock class which inherits from IHttpPostedFileAdapter.
