I'm working in a project that uses the new ASP.NET WebAPI. My current task is to accept an uploaded file. So far, I have used TDD to drive out the WebAPI code, but I've hit a wall with uploading. I'm currently following the advice found at http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-2, but there seems to be no way at all to drive this out of a unit test. In order to get at the file and form data, I have to use MultipartFormDataStreamProvider
, which is impossible to mock and/or override. Short of forsaking my TDD approach, what can I do?
Here's the code from the example:
public Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
The first problem is this line:
var provider = new MultipartFormDataStreamProvider(root);
For starters, to unit test this code, I need to be able to inject such a provider. It does WAY too much in that simple constructor call to be "newing it up" in line. There's got to be another way. (If not, WebAPI fails)
You can either create a unit test project when creating your application or add a unit test project to an existing application. This tutorial shows both methods for creating a unit test project. To follow this tutorial, you can use either approach.
I abstracted out a provider wrapper so I could mock those moving parts, something like
public interface IMultiPartFormDataStreamProviderWrapper : IDependency
{
string LocalFileName { get; }
MultipartFormDataStreamProvider Provider { get; }
}
public class MultiPartFormDataStreamProviderWrapper : IMultiPartFormDataStreamProviderWrapper
{
public const string UploadPath = "~/Media/Default/Vocabulary/";
private MultipartFormDataStreamProvider provider;
public MultiPartFormDataStreamProviderWrapper(IHttpContextAccessor httpContextAccessor)
{
provider = new CustomMultipartFormDataStreamProvider(httpContextAccessor.Current().Server.MapPath(UploadPath));
}
public string LocalFileName
{
get { return provider.FileData[0].LocalFileName; }
}
public MultipartFormDataStreamProvider Provider
{
get { return provider; }
}
}
So I could then do something like
if (Request.Content.IsMimeMultipartContent())
{
return Request.Content.ReadAsMultipartAsync(provider.Provider).ContinueWith(t =>
{
if (t.IsCanceled || t.IsFaulted)
return (object)new { success = false };
Not ideal, but gives some piece of mind. What do you think?
If you haven't thrown in the towel on Web API yet, you might try System.Net.Http.TestableMultipartStreamProviders, which are a drop-in rewrite of the Microsoft stream providers. Their benefit is that they rely on SystemWrapper for file operations, which means the file operations can be mocked in unit tests. The wiki gives some ideas about leveraging DI to make testing controllers less of a pain.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With