关于c#:测试使用HttpContext.Current.Request.Files的Web API方法?

Testing a Web API method that uses HttpContext.Current.Request.Files?

我试图编写一个使用HttpContext.Current.Request.Files的Web API方法的测试,经过详尽的搜索和实验后,我无法弄清楚如何对其进行模拟。被测试的方法如下所示:

1
2
3
4
5
6
7
[HttpPost]
public HttpResponseMessage Post()
{
    var requestFiles = HttpContext.Current.Request.Files;
    var file = requestFiles.Get(0);
    //do some other stuff...
}

我意识到还有其他与此类似的问题,但是它们并不能解决这种特定情况。

如果尝试模拟上下文,则会遇到Http*对象层次结构的问题。假设我像这样设置了各种模拟对象(使用Moq):

1
2
3
4
5
6
7
8
9
var mockFiles = new Mock<HttpFileCollectionBase>();
mockFiles.Setup(s => s.Count).Returns(1);
var mockFile = new Mock<HttpPostedFileBase>();
mockFile.Setup(s => s.InputStream).Returns(new MemoryStream());
mockFiles.Setup(s => s.Get(It.IsAny<int>())).Returns(mockFile.Object);
var mockRequest = new Mock<HttpRequestBase>();
mockRequest.Setup(s => s.Files).Returns(mockFiles.Object);
var mockContext = new Mock<HttpContextBase>();
mockContext.Setup(s => s.Request).Returns(mockRequest.Object);

尝试将其分配给当前上下文...

1
HttpContext.Current = mockContext.Object;

...导致编译器错误/红线,因为它是Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext'

我还尝试了对构造的控制器对象附带的各种上下文对象进行钻取,但是找不到一个是a)是控制器方法主体中HttpContext.Current调用的返回对象,并且b)提供了对标准属性,例如Files

1
2
3
var requestMsg = controller.Request;   //returns HttpRequestMessage
var context = controller.ControllerContext;  //returns HttpControllerContext
var requestContext = controller.RequestContext;   //read-only returns HttpRequestContext

还需要注意的一点是,我根本无法更改正在测试的控制器,因此无法更改构造函数以允许注入上下文。

有什么方法可以模拟HttpContext.Current.Request.Files以便在Web API中进行单元测试?

更新资料
尽管我不确定这是否会被团队接受,但我正在尝试按照Martin Liversage的建议将Post方法更改为使用Request.Content。当前看起来像这样:

1
2
3
4
5
6
7
public async Task<HttpResponseMessage> Post()
{
    var uploadFileStream = new MultipartFormDataStreamProvider(@"C:\temp");
    await Request.Content.ReadAsMultipartAsync(uploadFileStream);
    //do the stuff to get the file
    return ActionContext.Request.CreateResponse(HttpStatusCode.OK,"it worked!");
}

我的测试看起来与此类似:

1
2
3
4
5
6
7
8
9
10
var byteContent = new byte[]{};
var content = new MultipartContent { new ByteArrayContent(byteContent) };
content.Headers.Add("Content-Disposition","form-data");
var controllerContext = new HttpControllerContext
{
    Request = new HttpRequestMessage
        {
            Content = new MultipartContent { new ByteArrayContent(byteContent) }
        }
};

现在我在ReadAsMultipartAsync上遇到错误:

System.IO.IOException: Error writing MIME multipart body part to output stream. ---> System.InvalidOperationException: The stream provider of type 'MultipartFormDataStreamProvider' threw an exception. ---> System.InvalidOperationException: Did not find required 'Content-Disposition' header field in MIME multipart body part.


通过允许您模拟各种上下文对象,构建了Web API以支持单元测试。但是,通过使用HttpContext.Current,您正在使用使用HttpContext类的"旧式" System.Web代码,这使得无法对代码进行单元测试。

要使代码可进行单元测试,必须停止使用HttpContext.Current。在ASP.NET Web API中发送HTML表单数据:文件上载和多部分MIME中,您可以了解如何使用Web API上载文件。具有讽刺意味的是,此代码还使用HttpContext.Current来访问MapPath,但是在Web API中,您应该使用在IIS之外也可以使用的HostingEnvironment.MapPath。模拟后面的内容也是有问题的,但现在,我将重点放在您关于模拟请求的问题上。

不使用HttpContext.Current可以通过分配控制器的ControllerContext属性来对控制器进行单元测试:

1
2
3
4
5
6
7
8
9
var content = new ByteArrayContent( /* bytes in the file */ );
content.Headers.Add("Content-Disposition","form-data");
var controllerContext = new HttpControllerContext {
  Request = new HttpRequestMessage {
    Content = new MultipartContent { content }
  }
};
var controller = new MyController();
controller.ControllerContext = controllerContext;


接受的答案非常适合OP的问题。我想在此处添加源自Martin的解决方案,因为这是我在仅搜索如何模拟出Web API的Request对象时可以直接转到的页面,因此我可以添加Controller寻找的标头。我很难找到简单的答案:

1
2
3
4
5
6
   var controllerContext = new HttpControllerContext();
   controllerContext.Request = new HttpRequestMessage();
   controllerContext.Request.Headers.Add("Accept","application/xml");

   MyController controller = new MyController(MockRepository);
   controller.ControllerContext = controllerContext;

你在那里;这是一种创建控制器上下文的非常简单的方法,您可以通过该方法"模拟"出Request对象,并为Controller方法提供正确的标头。


我只是嘲笑发布的文件。我相信所有文件也可以通过这种方式来模拟。

This was in my controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private HttpPostedFileBase _postedFile;

/// <summary>
/// For mocking HttpPostedFile
/// </summary>
public HttpPostedFileBase PostedFile
{
    get
    {
        if (_postedFile != null) return _postedFile;
        if (HttpContext.Current == null)
        {
            throw new InvalidOperationException("HttpContext not available");
        }
        return new HttpContextWrapper(HttpContext.Current).Request.Files[0];
    }
    set { _postedFile = value; }
}

[HttpPost]
public MyResponse Upload()
{
    if (!ValidateInput(this.PostedFile))
    {
        return new MyResponse
        {
            Status ="Input validation failed"
        };
    }
}

private bool ValidateInput(HttpPostedFileBase file)
{
    if (file.ContentLength == 0)
        return false;

    if (file.ContentType !="test")
        return false;

    if (file.ContentLength > (1024 * 2048))
        return false;

    return true
}

This was in my Unit test case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void Test1()
{
    var controller = new Mock<MyContoller>();
    controller.Setup(x => x.Upload).Returns(new CustomResponse());

    controller.Request = new HttpRequestMessage();
    controller.Request.Content = new StreamContent(GetContent());
    controller.PostedFile = GetPostedFile();

    var result = controller.Upload().Result;
}

private HttpPostedFileBase GetPostedFile()
{
    var postedFile = new Mock<HttpPostedFileBase>();
    using (var stream = new MemoryStream())
    using (var bmp = new Bitmap(1, 1))
    {
        var graphics = Graphics.FromImage(bmp);
        graphics.FillRectangle(Brushes.Black, 0, 0, 1, 1);
        bmp.Save(stream, ImageFormat.Jpeg);
        postedFile.Setup(pf => pf.InputStream).Returns(stream);
        postedFile.Setup(pf => pf.ContentLength).Returns(1024);
        postedFile.Setup(pf => pf.ContentType).Returns("bmp");
    }
    return postedFile.Object;
}

Although, I was not able to successfully mock the HTTPContext. But, I
was able to mock the file upload.