2009-09-18 5 views
4

J'ai une action qui renvoie soit un FileContentResult ou un NotModifiedResult, qui est un type de résultat personnalisé qui renvoie HTTP 304 pour indiquer que la ressource demandée n'a pas été modifiée , comme ceci:Comment obtenir et définir les en-têtes http dans une action, de manière testable

[ReplaceMissingPicture(Picture = "~/Content/Images/nothumbnail.png", MimeType = "image/png")] 
public ActionResult Thumbnail(int id) 
{ 
    var item = Service.GetItem(id); 

    var requestTag = Request.Headers["If-None-Match"] ?? string.Empty; 
    var tag = Convert.ToBase64String(item.Version.ToArray()); 

    if (tag == requestTag) 
    { 
     return new NotModifiedResult(); 
    } 

    if (item.Thumbnail != null) 
    { 
     var thumbnail = item.Thumbnail.ToArray(); 
     var mime = item.PictureMime; 

     Response.AppendHeader("ETag", tag); 

     return File(thumbnail, mime); 
    } 
    else 
    { 
     return null; 
    } 
} 

cette action doit accéder à l'objet de réponse, ce qui est bien sûr pas présent au cours des essais, de sorte que cette action fait invérifiable. Je pourrais ajouter des instructions conditionnelles autour de lui, de sorte qu'il s'exécute pendant le test, mais je ne peux pas tester les en-têtes correctement définis.

Quelle serait une solution à ce problème? FYI, le filtre ReplaceMissingPicture retourne une ressource spécifique dans le cas où null a été retourné à partir de cette action, pour garder l'appel MapPath() hors du contrôleur pour la même raison.

+0

Qu'en est-controllerInstance .HttpContext.Response.Headers ["ETag"] dans une méthode de test? –

+0

Vous devez en particulier vous moquer de cela si vous voulez un HttpContext pendant les tests unitaires. Le but ici est de faire en sorte que votre méthode d'action ne dépende pas de HttpContext. –

Répondre

1

La première étape serait de créer une interface qui simplifie les services dont vous avez besoin: -

public interface IHeaders 
    { 
     public string GetRequestHeader(string headerName); 
     public void AppendResponseHeader(string headerName, string headerValue); 
    } 

maintenant créer une implémentation par défaut: -

public Headers : IHeaders 
{ 
     public string GetRequestHeader(string headerName) 
     { 
      return HttpContext.Current.Request[headerName]; 
     } 
     public void AppendResponseHeader(string headerName, string headerValue) 
     { 
      HttpContext.Current.Response.AppendHeader(headerName, headerValue); 
     } 
} 

Maintenant, ajoutez un nouveau champ à votre contrôleur : -

 private IHeaders myHeadersService; 

ajouter nouveau constructeur à votre contrôleur: -

 public MyController(IHeaders headersService) 
    { 
     myHeadersService = headersService; 
    } 

modifier ou ajouter le constructeur par défaut: -

public MyController() 
    { 
     myHeadersService = new Headers(); 
    } 

maintenant dans votre code d'action utilisé myHeadersService au lieu des objets de réponse et demande.

Lors de vos tests, créez votre propre implémentation de l'interface IHeaders pour émuler/tester le code Action et transmettre cette implémentation lors de la construction du contrôleur.

+0

Cela ressemble à une solution respectable! –

+0

Qu'est-ce que cela devrait nous donner de plus que d'utiliser ViewData ou toute autre collection de données prête? –

+0

Le ViewData n'est pas un bon endroit, en partie parce qu'il n'est pas sécurisé. En outre, je ne suis pas convaincu qu'un FileContentResult possède une ViewData. –

1

Que diriez-vous de créer une sous-classe de FileResult --say ETagFileResult --Que dans sa méthode ExecuteResult() définit l'en-tête ETag, puis par défaut la mise en œuvre de la classe de base? Vous pouvez tester cette classe avec un contexte simulé (comme vous le faites sans doute avec votre NotModifiedResult) pour être sûr qu'elle fait la bonne chose. Et supprimer la complication entière du test du contrôleur.

A défaut, il est possible de définir un contexte simulé sur le contrôleur dans votre test (après l'instanciation de la classe, avant d'appeler la méthode d'action). Voir this question, par exemple. Mais cela semble être plus de travail.

(aussi, en passant, on dirait que vous citez la valeur de l'étiquette deux fois il: une fois quand tag est réglé, et une fois de plus lorsque vous définissez réellement l'en-tête ....)

+0

C'était initialement ma pensée aussi, mais à la place j'ai un résultat "wrapper" qui est une classe plus générique, comme TagResult . –

+0

Aussi, à propos de la citation: oui, cela a été corrigé dans mon application. Je vais mettre à jour l'exemple de code. TY. –

+0

Ouais, l'encapsuleur n'est pas une mauvaise idée du tout: vous seriez capable d'utiliser cela avec les résultats de fichiers et les vues HTML normales. –

Questions connexes