2016-10-05 1 views
2

Je crée des tests unitaires pour ma classe ASP .NET MVC Controller et je suis tombé sur quelques erreurs étranges:problèmes dans la création de test unitaire pour ASP .NET MVC

Mon code de commande est ci-dessous:

[HttpPost] 
[ValidateAntiForgeryToken] 
public ActionResult Delete(JournalViewModel journal) 
{ 
    var selectedJournal = Mapper.Map<JournalViewModel, Journal>(journal); 

    var opStatus = _journalRepository.DeleteJournal(selectedJournal); 
    if (!opStatus.Status) 
     throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); 

    return RedirectToAction("Index"); 
} 

Mon code de test est ci-dessous:

[TestMethod] 
public void Delete_Journal() 
{ 
    // Arrange 

    // Simulate PDF file 
    HttpPostedFileBase mockFile = Mock.Create<HttpPostedFileBase>(); 
    Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf"); 
    Mock.Arrange(() => mockFile.ContentLength).Returns(255); 

    // Create view model to send. 
    JournalViewModel journalViewModel = new JournalViewModel(); 
    journalViewModel.Id = 1; 
    journalViewModel.Title = "Test"; 
    journalViewModel.Description = "TestDesc"; 
    journalViewModel.FileName = "TestFilename.pdf"; 
    journalViewModel.UserId = 1; 
    journalViewModel.File = mockFile; // Add simulated file 

    Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus 
    { 
     Status = true 
    }); 

    // Act 
    PublisherController controller = new PublisherController(journalRepository, membershipRepository); 
    RedirectToRouteResult result = controller.Delete(journalViewModel) as RedirectToRouteResult; 

    // Assert 
    Assert.AreEqual(result.RouteValues["Action"], "Index"); 
} 

problème 1 - cartographie Exception:

Chaque fois que je lance mon test, je l'exception suivante:

Nom du test: test Delete_Journal
FullName: Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
Source Test: \ Source \ Journaux .Web.Tests \ Controllers \ PublisherControllerTest.cs : ligne 132
Résultat d'essai: Durée de l'essai a échoué: 0: 00: 00,3822468

Résultat StackTrace: à Journals.Web.Controllers.PublisherController.Delete (J Journal ournalViewModel ) dans \ Source \ Journals.Web \ contrôleurs \ PublisherController.cs: ligne 81 à Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal() dans \ Source \ Journals.Web.Tests \ Controllers \ PublisherControllerTest.cs: ligne 156 Résultat message: méthode d'essai Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal a jeté exception: AutoMapper.AutoMapperMappingException: Type manquant configuration de la carte ou la cartographie non pris en charge.

types de cartographie: JournalViewModel -> Journal Journals.Model.JournalViewModel -> Journals.Model.Journal

Chemin de destination: Journal

valeur Source: Journals.Model.JournalViewModel

Il semble qu'il y ait un problème de mappage entre les classes JournalViewModel et Journal, mais je ne sais pas où c'est. J'ai ajouté ce code à la Application_Start dans Global.asax.cs:

Mapper.CreateMap<Journal, JournalViewModel>(); 
Mapper.CreateMap<JournalViewModel, Journal>(); 

et la cartographie de Journal à JournalViewModel fonctionne.

En fin de compte j'ai essayé d'ajouter Mapper.CreateMap<JournalViewModel, Journal>(); comme la première ligne de la méthode Delete et puis tout fonctionne, mais je ne sais pas pourquoi.

Problème 2 - HTML Exception

Une fois la mise en correspondance est en cours d'exécution avec la solution de contournement ci-dessus, j'ai un problème dans lequel la propriété Status de var opStatus = _journalRepository.DeleteJournal(selectedJournal); est toujours faux, même si je Mock pour modifier et faire c'est toujours vrai. Cela provoque le lancement d'une exception HTML qui ne devrait pas se produire.

EDIT

j'ai changé dans mon Application_Start à:

Mapper.Initialize(cfg => 
{ 
    cfg.CreateMap<Journal, JournalViewModel>(); 
    cfg.CreateMap<JournalViewModel, Journal>(); 
}); 

Mais je toujours la même erreur.

EDIT - Problème 2 Résolu

Il se trouve que j'ai oublié d'ajouter le mappage à ma classe de test unitaire, donc je ne les suivantes:

[TestInitialize] 
public void TestSetup() 
{ 
    // Create necessary mappings 
    Mapper.CreateMap<Journal, JournalViewModel>(); 
    Mapper.CreateMap<JournalViewModel, Journal>(); 

    //...other code omitted for brevity 
} 

Et il se trouve que cette était la source du problème. Je pense que puisque le Global.asax.cs Application_Start() n'est jamais appelé dans les tests unitaires, le Mapping n'est jamais créé, donc j'ai dû le faire moi-même lors de l'initialisation des tests unitaires.

+2

Vous avez probablement une sorte de 'TestBase' classe wh Avant d'appeler votre code de configuration de mappage. Le 'Mapper 'statique dans votre test ne sait rien de la configuration de mappage de votre' Global.asax.cs'. –

+0

Je suggère d'utiliser une méthode statique pour créer toutes les cartes, puis appeler cette méthode statique dans le cadre de votre configuration de test. En ce qui concerne le deuxième problème, pourriez-vous me dire ce qu'est JournalRepository. L'adoptez-vous comme une dépendance? –

+0

Quelle version d'Automapper utilisez-vous?Je suggère d'utiliser des abstractions ('IMapper') à la place de l'API statique car vous avez déjà découvert les difficultés dans les tests unitaires des API statiques – Nkosi

Répondre

3

Problème 1

Automapper a à la fois un Static and Instance API. Vous devriez envisager d'utiliser l'API d'instance avec IMapper et l'injecter dans votre contrôleur.

public class PublisherController : Controller { 
    private readonly IMapper mapper; 

    public PublisherController(IJournalRepository journalRepository, IMembershipRepositry membershipRepository, IMapper mapper) { 
     //...other code omitted for brevity 
     this.mapper = mapper; 
    } 

    //...other code omitted for brevity 

    [HttpPost] 
    [ValidateAntiForgeryToken] 
    public ActionResult Delete(JournalViewModel journal) { 
     var selectedJournal = mapper.Map<JournalViewModel, Journal>(journal); 

     var opStatus = _journalRepository.DeleteJournal(selectedJournal); 
     if (!opStatus.Status) 
      throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); 

     return RedirectToAction("Index"); 
    } 
} 

Cela permettrait de mieux simuler/simuler/configuration de la cartographie au besoin. Vous devez vous assurer de configurer IMapper pour l'injection de dépendance dans vos contrôleurs.

si vous ne pouvez pas modifier l'api instance, alors vous devez vous assurer que le mappeur est Initialiser avant d'exécuter les essais

Mapper.Initialize(cfg => { 
    cgf.CreateMap<JournalViewModel, Journal>(); 
}); 

Problème 2

votre disposition dans le test est

Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus 
{ 
    Status = true 
}); 

Ce que vous avez réalisé ne fonctionnera pas pour les cas où vous appelez journalRepository.DeleteJournal avec un Actu al par exemple. En supposant que vous utilisez JustMock de Telerik, vous devriez organiser un argument plus flexible.

Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(new OperationStatus 
{ 
    Status = true 
}); 

Source: Handling Arguments in JustMock Arrangements

test complet: API instance

[TestMethod] 
public void Delete_Journal() { 
    // Arrange 

    //Configure mapping just for this test but something like this 
    //should be in accessible from your composition root and called here. 
    var config = new MapperConfiguration(cfg => { 
     cfg.CreateMap<Journal, JournalViewModel>(); 
     cfg.CreateMap<JournalViewModel, Journal>(); 
    }); 

    var mapper = config.CreateMapper(); // IMapper 

    // Simulate PDF file 
    var mockFile = Mock.Create<HttpPostedFileBase>(); 
    Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf"); 
    Mock.Arrange(() => mockFile.ContentLength).Returns(255); 

    // Create view model to send. 
    var journalViewModel = new JournalViewModel(); 
    journalViewModel.Id = 1; 
    journalViewModel.Title = "Test"; 
    journalViewModel.Description = "TestDesc"; 
    journalViewModel.FileName = "TestFilename.pdf"; 
    journalViewModel.UserId = 1; 
    journalViewModel.File = mockFile; // Add simulated file 

    var status = new OperationStatus { 
     Status = true 
    }; 

    Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(status); 

    var controller = new PublisherController(journalRepository, membershipRepository, mapper); 

    // Act   
    var result = controller.Delete(journalViewModel) as RedirectToRouteResult; 

    // Assert 
    Assert.AreEqual(result.RouteValues["Action"], "Index"); 
} 

test complet: API statique

[TestMethod] 
public void Delete_Journal() { 
    // Arrange 

    //Configure mapping just for this test but something like this 
    //should be in accessible from your composition root and called here. 
    Mapper.Initialize(cfg => { 
     cfg.CreateMap<Journal, JournalViewModel>(); 
     cfg.CreateMap<JournalViewModel, Journal>(); 
    }); 

    // Simulate PDF file 
    var mockFile = Mock.Create<HttpPostedFileBase>(); 
    Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf"); 
    Mock.Arrange(() => mockFile.ContentLength).Returns(255); 

    // Create view model to send. 
    var journalViewModel = new JournalViewModel(); 
    journalViewModel.Id = 1; 
    journalViewModel.Title = "Test"; 
    journalViewModel.Description = "TestDesc"; 
    journalViewModel.FileName = "TestFilename.pdf"; 
    journalViewModel.UserId = 1; 
    journalViewModel.File = mockFile; // Add simulated file 

    var status = new OperationStatus { 
     Status = true 
    }; 

    Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).Returns(status); 

    var controller = new PublisherController(journalRepository, membershipRepository); 

    // Act   
    var result = controller.Delete(journalViewModel) as RedirectToRouteResult; 

    // Assert 
    Assert.AreEqual(result.RouteValues["Action"], "Index"); 
} 
+0

Salut, j'ai changé mon code de test dans le problème 2 et tout a fonctionné! :) – Felipe

+1

En ce qui concerne le problème 1, existe-t-il un moyen de faire fonctionner le Mappeur avec les méthodes statiques? Sinon, je devrais changer beaucoup de mon code actuel. – Felipe

+1

Vérifiez la réponse mise à jour. initialiser le mappeur avant d'exécuter le test. Si réponse utile, votez Est-ce que la réponse résout votre problème puis marquez comme – Nkosi