2015-07-02 7 views
8

Я пытаюсь написать тест для метода Web API, который использует HttpContext.Current.Request.Files, и после исчерпывающего поиска и экспериментов я не могу понять, как насмехаться над ним. Метод тестируется выглядит следующим образом:Тестирование метода веб-API, использующего HttpContext.Current.Request.Files?

[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) является объектом возврата вызова методав теле метода контроллера и 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?

Update
Хотя я не уверен, что это будет принято командой, я экспериментировал с изменением метода 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. ---> 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.

+1

В качестве альтернативы, если вы действительно не можете изменить свой код, чтобы удалить прямую связь с зависимостью «HttpContext.Current», существует длинный отложенный способ сделать это [через отражение] (http: // stackoverflow. com/a/31177399/314291) – StuartLC

ответ

18

Web API имеет были созданы для поддержки модульного тестирования, позволяя вам издеваться над различными объектами контекста. Однако, используя HttpContext.Current, вы используете код «старого стиля» System.Web, который использует класс HttpContext, который делает невозможным модульное тестирование вашего кода.

Для того чтобы ваш код был единым тестируемым, вам необходимо прекратить использование HttpContext.Current. В файле Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME вы можете увидеть, как загружать файлы с помощью веб-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; 
+0

Благодарим за отзыв. Пожалуйста, см. Мое обновление выше о содержании. –

+0

@ AJ: Я обновил код. Вы должны установить заголовок 'Content-Disposition' внутреннего содержимого, а не многоконтентного содержимого. –

+0

@MartinLiversage - Я пытаюсь заставить это работать, и вам не повезло - если у вас есть время, вы можете взглянуть на это, пожалуйста? http://stackoverflow.com/questions/44073646/httprequestmessage-content-disposition-null-when-unit-testing – Darren

2

Принятая ответ идеально подходит для вопроса Ор в. Я хотел добавить мое решение здесь, которое происходит от Мартина, поскольку это страница, на которую я был направлен, когда просто искал, как выкачать объект Request для 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; 

И вот вы где; очень простой способ создать контекст контроллера, с помощью которого вы можете «отморозить» объект «Запрос» и предоставить правильные заголовки для вашего метода контроллера.