J'espère que cette question donne des réponses intéressantes parce que c'est celle qui m'a dérangé pendant un certain temps.
Existe-t-il une valeur réelle dans le test unitaire d'un contrôleur dans ASP.NET MVC?
Ce que je veux dire par là, c'est que la plupart du temps (et je ne suis pas un génie), mes méthodes de contrôleur sont, même à leur plus complexe quelque chose comme ça:
public ActionResult Create(MyModel model)
{
// start error list
var errors = new List<string>();
// check model state based on data annotations
if(ModelState.IsValid)
{
// call a service method
if(this._myService.CreateNew(model, Request.UserHostAddress, ref errors))
{
// all is well, data is saved,
// so tell the user they are brilliant
return View("_Success");
}
}
// add errors to model state
errors.ForEach(e => ModelState.AddModelError("", e));
// return view
return View(model);
}
La plupart des tâches lourdes sont effectuées par le pipeline MVC ou ma bibliothèque de services.
Alors peut-être que les questions à poser pourraient être:
Request.UserHostAddress
et ModelState
avec une NullReferenceException? Dois-je essayer de les moquer?Je pense que mon point vraiment est que faire ce qui suit semble tout à fait inutile et faux
[TestMethod]
public void Test_Home_Index()
{
var controller = new HomeController();
var expected = "Index";
var actual = ((ViewResult)controller.Index()).ViewName;
Assert.AreEqual(expected, actual);
}
Évidemment, je suis obtus avec cet exemple exagérément inutile, mais quelqu'un a-t-il la sagesse d'ajouter ici?
Au plaisir ... Merci.
Même pour quelque chose d'aussi simple, un test unitaire aura plusieurs objectifs
Pour cette action particulière, je testerais les éléments suivants
Vous avez souligné la vérification de Request and Model pour NullReferenceException et je pense que ModelState.IsValid se chargera de gérer NullReference for Model.
Se moquer de la demande vous permet de vous prémunir contre une demande nulle qui est généralement impossible en production, je pense, mais peut se produire dans un test unitaire. Dans un test d'intégration, il vous permettrait de fournir différentes valeurs UserHostAddress (une demande est toujours entrée par l'utilisateur en ce qui concerne le contrôle et doit être testée en conséquence)
Mes contrôleurs sont également très petits. La plupart de la "logique" des contrôleurs est gérée à l'aide d'attributs de filtre (intégrés et manuscrits). Donc, mon contrôleur n'a généralement qu'une poignée d'emplois:
ActionResult
La plupart des liaisons de modèle sont effectuées automatiquement par ASP.NET MVC. DataAnnotations gère également la majeure partie de la validation.
Même avec si peu de choses à tester, je les écris généralement. Fondamentalement, je teste que mes référentiels sont appelés et que le type ActionResult
correct est renvoyé. J'ai une méthode pratique pour ViewResult
pour m'assurer que le bon chemin de vue est retourné et que le modèle de vue ressemble à ce que j'attends. J'en ai un autre pour vérifier que le bon contrôleur/action est défini pour RedirectToActionResult
. J'ai d'autres tests pour JsonResult
, etc. etc.
Un résultat malheureux de la sous-classification de la classe Controller
est qu'elle fournit de nombreuses méthodes pratiques qui utilisent HttpContext
en interne. Cela rend difficile le test unitaire du contrôleur. Pour cette raison, je place généralement des appels dépendants de HttpContext
derrière une interface et je transmets cette interface au constructeur du contrôleur (j'utilise l'extension Web Ninject pour créer mes contrôleurs pour moi). Cette interface est généralement l'endroit où je colle les propriétés d'assistance pour accéder à la session, aux paramètres de configuration, aux assistants IPrinciple et URL.
Cela demande beaucoup de diligence raisonnable, mais je pense que cela en vaut la peine.
Évidemment, certains contrôleurs sont beaucoup plus complexes que cela, mais basés uniquement sur votre exemple:
Que se passe-t-il si myService lève une exception?
En remarque.
En outre, je remettrais en question la sagesse de transmettre une liste par référence (ce n'est pas nécessaire car c # passe par référence de toute façon mais même si ce n'était pas le cas) - passer une action errorAction (Action) que le service peut ensuite utiliser pour pomper des messages d'erreur vers qui pourrait ensuite être gérée comme vous le souhaitez (peut-être que vous voulez l'ajouter à la liste, peut-être que vous voulez ajouter une erreur de modèle, peut-être que vous voulez l'enregistrer).
Dans votre exemple:
au lieu d'erreurs ref, faites (chaîne s) => ModelState.AddModelError ("", s) par exemple.