测试使用 HttpContext.Current.Request.Files 的 Web API 方法?
Testing a Web API method that uses HttpContext.Current.Request.Files?
我正在尝试为使用 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 questions similar 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;
...导致编译器 error/redline 因为它 Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext'
.
我还尝试深入研究构造的控制器对象附带的各种上下文对象,但找不到一个 a) 是控制器中 HttpContext.Current
调用的 return 对象方法体和 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 中进行单元测试?
更新
虽然我不确定这会被团队接受,但我正在尝试根据 Martin Liversage 的建议将 Post 方法更改为使用 Request.Content
。它目前看起来像这样:
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. ---> 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
,您正在使用 "old-style" System.Web
代码,该代码使用 HttpContext
class,这使得无法对您的代码进行单元测试。
要让您的代码可以进行单元测试,您必须停止使用 HttpContext.Current
。在 Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME 中,您可以了解如何使用 Web API 上传文件。具有讽刺意味的是,此代码还使用 HttpContext.Current
来访问 MapPath
,但在 Web API 中,您应该使用也在 IIS 之外工作的 HostingEnvironment.MapPath
。模拟后者也是有问题的,但现在我专注于你关于模拟请求的问题。
不使用 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;
接受的答案非常适合 OP 的问题。我想在这里添加我的解决方案,它源自 Martin 的,因为这是我在简单搜索如何为 Web API 模拟请求 object 时被定向到的页面,因此我可以添加 headers 我的控制器正在寻找。我很难找到简单的答案:
var controllerContext = new HttpControllerContext();
controllerContext.Request = new HttpRequestMessage();
controllerContext.Request.Headers.Add("Accept", "application/xml");
MyController controller = new MyController(MockRepository);
controller.ControllerContext = controllerContext;
你来了;一种创建控制器上下文的非常简单的方法,您可以使用它 "Mock" 发出请求 object 并为您的控制器方法提供正确的 headers。
我只是嘲笑了发布的文件。我相信所有的文件也可以这样模拟。
This was in my controller
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
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.
我正在尝试为使用 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 questions similar 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;
...导致编译器 error/redline 因为它 Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext'
.
我还尝试深入研究构造的控制器对象附带的各种上下文对象,但找不到一个 a) 是控制器中 HttpContext.Current
调用的 return 对象方法体和 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 中进行单元测试?
更新
虽然我不确定这会被团队接受,但我正在尝试根据 Martin Liversage 的建议将 Post 方法更改为使用 Request.Content
。它目前看起来像这样:
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. ---> 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
,您正在使用 "old-style" System.Web
代码,该代码使用 HttpContext
class,这使得无法对您的代码进行单元测试。
要让您的代码可以进行单元测试,您必须停止使用 HttpContext.Current
。在 Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME 中,您可以了解如何使用 Web API 上传文件。具有讽刺意味的是,此代码还使用 HttpContext.Current
来访问 MapPath
,但在 Web API 中,您应该使用也在 IIS 之外工作的 HostingEnvironment.MapPath
。模拟后者也是有问题的,但现在我专注于你关于模拟请求的问题。
不使用 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;
接受的答案非常适合 OP 的问题。我想在这里添加我的解决方案,它源自 Martin 的,因为这是我在简单搜索如何为 Web API 模拟请求 object 时被定向到的页面,因此我可以添加 headers 我的控制器正在寻找。我很难找到简单的答案:
var controllerContext = new HttpControllerContext();
controllerContext.Request = new HttpRequestMessage();
controllerContext.Request.Headers.Add("Accept", "application/xml");
MyController controller = new MyController(MockRepository);
controller.ControllerContext = controllerContext;
你来了;一种创建控制器上下文的非常简单的方法,您可以使用它 "Mock" 发出请求 object 并为您的控制器方法提供正确的 headers。
我只是嘲笑了发布的文件。我相信所有的文件也可以这样模拟。
This was in my controller
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
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.