【问题标题】:Testing a Web API method that uses HttpContext.Current.Request.Files?测试使用 HttpContext.Current.Request.Files 的 Web API 方法?
【发布时间】:2015-07-02 15:42:06
【问题描述】:

我正在尝试为使用 HttpContext.Current.Request.Files 的 Web API 方法编写测试,经过详尽的搜索和实验后,我无法弄清楚如何模拟它。正在测试的方法如下所示:

[HttpPost]
public HttpResponseMessage Post()
{
    var requestFiles = HttpContext.Current.Request.Files;
    var file = requestFiles.Get(0);
    //do some other stuff...
}

我知道有other questionssimilar to this,但他们没有解决这个具体情况。

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

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);

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

HttpContext.Current = mockContext.Object;

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

我还尝试深入研究构造的控制器对象附带的各种上下文对象,但找不到 a) 是控制器方法主体中 HttpContext.Current 调用的返回对象和 b) 提供对标准HttpRequest 属性,如Files

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 中进行单元测试?

更新
虽然我不确定这是否会被团队接受,但我正在尝试将 Post 方法更改为使用Request.Content,正如Martin Liversage 所建议的那样。它目前看起来像这样:

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!");
}

我的测试看起来像这样:

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. ---&gt; System.InvalidOperationException: The stream provider of type 'MultipartFormDataStreamProvider' threw an exception. ---&gt; System.InvalidOperationException: Did not find required 'Content-Disposition' header field in MIME multipart body part.

【问题讨论】:

  • 顺便说一句,如果你真的不能改变你的代码来移除对HttpContext.Current依赖的直接耦合,那么this via reflection有很长的路要走

标签: c# unit-testing asp.net-web-api moq httpcontext


【解决方案1】:

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

要使您的代码可进行单元测试,您必须停止使用HttpContext.Current。在Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME 你可以看到如何使用 Web API 上传文件。具有讽刺意味的是,这段代码还使用HttpContext.Current 来访问MapPath,但在Web API 中您应该使用HostingEnvironment.MapPath,它也可以在IIS 之外工作。模拟后者也是有问题的,但现在我专注于你关于模拟请求的问题。

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

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;

【讨论】:

【解决方案2】:

接受的答案非常适合 OP 的问题。我想在这里添加我的解决方案,它源自 Martin 的解决方案,因为这是我在简单搜索如何模拟 Web API 的请求对象时被定向到的页面,以便我可以添加我的控制器正在寻找的标题。我很难找到简单的答案:

   var controllerContext = new HttpControllerContext();
   controllerContext.Request = new HttpRequestMessage();
   controllerContext.Request.Headers.Add("Accept", "application/xml");

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

你来了;一种创建控制器上下文的非常简单的方法,您可以使用它“模拟”请求对象并为您的控制器方法提供正确的标头。

【讨论】:

    【解决方案3】:

    我只嘲笑了发布的文件。我相信所有文件也可以这样模拟。

    这是在我的控制器中

    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
    }
    

    这是在我的单元测试用例中

    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;
    }
    

    虽然,我无法成功模拟 HTTPContext。但是我 能够模拟文件上传。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-20
      • 2012-09-28
      • 2015-09-12
      • 1970-01-01
      相关资源
      最近更新 更多