it-roy-ru.com

Тестирование метода веб-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...
}

Я понимаю, что есть другие вопросыпохожие на это , но они не касаются этой конкретной ситуации. 

Если я пытаюсь смоделировать контекст, у меня возникают проблемы с иерархией объектов 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;

... приводит к ошибке компилятора/redline, потому что это Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext'.

Я также пробовал детализировать различные контекстные объекты, которые поставляются с созданным объектом контроллера, но не могу найти объект, который а) является возвращаемым объектом вызова HttpContext.Current в теле метода контроллера и б) обеспечивает доступ к стандартным свойствам 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. ---> 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.

8
AJ.

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

Чтобы ваш код можно было тестировать на модуле, вы должны прекратить использование HttpContext.Current. В Отправка данных формы HTML в ASP.NET Web API: выгрузка файлов и 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;
21
Martin Liversage

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

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

2
iGanja

Я издевался только над опубликованным файлом. Я полагаю, что все файлы также могут быть подделаны таким образом.

Это было в моем контроллере

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. Но я был в состоянии смоделировать загрузку файла.

0
Abhishek