J'essaie d'écrire un test pour une méthode d'API Web qui utilise HttpContext.Current.Request.Files
et après une recherche et une expérimentation exhaustives, je ne sais pas comment me moquer de cette méthode. La méthode en cours de test ressemble à ceci:
[HttpPost]
public HttpResponseMessage Post()
{
var requestFiles = HttpContext.Current.Request.Files;
var file = requestFiles.Get(0);
//do some other stuff...
}
Je me rends compte qu'il y a d'autres questionssimilaires à ceci , mais elles ne traitent pas de cette situation spécifique.
Si je tente de simuler le contexte, je rencontre des problèmes avec la hiérarchie des objets Http*
. Supposons que je mette en place divers objets factices (en utilisant Moq ) comme ceci:
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);
Tenter de l'assigner au contexte actuel ...
HttpContext.Current = mockContext.Object;
... entraîne une erreur/ligne rouge du compilateur, car il Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext'
.
J'ai également essayé d'explorer divers objets de contexte fournis avec l'objet contrôleur construit, mais je ne trouve aucun objet qui a) est l'objet de retour d'un appel HttpContext.Current
dans le corps de la méthode du contrôleur et b) permet d'accéder aux propriétés HttpRequest
standard, telles que Files
.
var requestMsg = controller.Request; //returns HttpRequestMessage
var context = controller.ControllerContext; //returns HttpControllerContext
var requestContext = controller.RequestContext; //read-only returns HttpRequestContext
Il est également important de noter que je ne peux pas du tout changer le contrôleur que je teste, je ne peux donc pas changer le constructeur pour permettre l'injection du contexte.
Existe-t-il un moyen de simuler HttpContext.Current.Request.Files
pour les tests unitaires dans Web API?
Mettre à jour
Bien que je ne sois pas certain que cela sera accepté par l’équipe, j’essaie de changer la méthode de publication pour utiliser Request.Content
, comme suggéré par Martin Liversage . Il ressemble actuellement à quelque chose comme ça:
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!");
}
Mon test ressemble à ceci:
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) }
}
};
Maintenant, je reçois une erreur sur 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.
La Web API a été conçue pour prendre en charge le test unitaire en vous permettant de simuler divers objets de contexte. Cependant, en utilisant HttpContext.Current
, vous utilisez un code __ "ancien" de System.Web
qui utilise la classe HttpContext
, ce qui rend impossible le test unitaire du code.
Pour permettre à votre code d'être testable par unité, vous devez cesser d'utiliser HttpContext.Current
. Dans Envoi de données de formulaire HTML dans l'API Web ASP.NET: Téléchargement de fichier et MIME en plusieurs parties vous pouvez voir comment télécharger des fichiers à l'aide de l'API Web. Ironiquement, ce code utilise également HttpContext.Current
pour accéder à MapPath
, mais dans l'API Web, vous devez utiliser HostingEnvironment.MapPath
, qui fonctionne également en dehors de IIS. Se moquer de ce dernier est également problématique, mais pour l'instant, je me concentre sur votre question concernant le fait de se moquer de la demande.
Ne pas utiliser HttpContext.Current
vous permet de tester votre contrôleur en affectant la propriété ControllerContext
du contrôleur:
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;
La réponse acceptée est parfaite pour la question du PO. Je voulais ajouter ici ma solution, qui provient de celle de Martin, car c'est la page à laquelle j'étais destiné lorsque je cherchais simplement à simuler l'objet Request pour l'API Web afin que je puisse ajouter les en-têtes que mon contrôleur recherche. J'ai eu du mal à trouver la réponse simple:
var controllerContext = new HttpControllerContext();
controllerContext.Request = new HttpRequestMessage();
controllerContext.Request.Headers.Add("Accept", "application/xml");
MyController controller = new MyController(MockRepository);
controller.ControllerContext = controllerContext;
Et vous voila un moyen très simple de créer un contexte de contrôleur avec lequel vous pouvez "simuler" l’objet Request et fournir les en-têtes appropriés pour votre méthode de contrôleur.
Je me suis moqué juste du fichier posté. Je crois que tous les fichiers peuvent également être moqués de cette façon.
C'était dans mon contrôleur
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
}
C'était dans mon cas de test unitaire
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;
}
Bien que, je n'ai pas pu moqué avec succès le HTTPContext. Mais je a pu se moquer du téléchargement de fichier.