Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a unit-testable way to upload files to ASP.NET WebAPI?

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)

like image 647
Byron Sommardahl Avatar asked Aug 21 '12 23:08

Byron Sommardahl


People also ask

Is it possible to unit test Web API?

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.


2 Answers

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?

like image 113
Brendan Avatar answered Oct 11 '22 13:10

Brendan


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.

like image 28
ladenedge Avatar answered Oct 11 '22 12:10

ladenedge