测试使用HttpContext.Current.Request.Files的Web API方法?

我正在尝试编写一个使用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... } 

我意识到还有其他 类似的 问题 ,但它们没有解决这个具体情况。

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

 var mockFiles = new Mock(); mockFiles.Setup(s => s.Count).Returns(1); var mockFile = new Mock(); mockFile.Setup(s => s.InputStream).Returns(new MemoryStream()); mockFiles.Setup(s => s.Get(It.IsAny())).Returns(mockFile.Object); var mockRequest = new Mock(); mockRequest.Setup(s => s.Files).Returns(mockFiles.Object); var mockContext = new Mock(); 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中进行unit testing?

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

 public async Task 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. ---> 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以支持unit testing。 但是,通过使用HttpContext.Current您使用的是“旧式” System.Web代码,该代码使用HttpContext类,这使得无法对代码进行unit testing。

为了使您的代码可以unit testing,您必须停止使用HttpContext.Current 。 在ASP.NET Web API中发送HTML表单数据:文件上载和多部分MIME,您可以看到如何使用Web API上载文件。 具有讽刺意味的是,此代码还使用HttpContext.Current来访问MapPath但在Web API中,您应该使用也在IIS外部工作的HostingEnvironment.MapPath 。 嘲笑后者也存在问题,但是现在我专注于你关于嘲笑请求的问题。

不使用HttpContext.Current允许您通过分配ControllerContext属性来对控制器进行unit testing:

 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’s,因为这是我在简单搜索如何模拟Web API的Request对象时所指向的页面,因此我可以添加Controller正在寻找的头文件。 我很难找到简单的答案:

  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方法提供正确的标头。