2010-03-13 16 views
20

Ho un controllore che implementa una semplice operazione Aggiungi su un'entità e reindirizza alla pagina di dettagli:Unità test di un controllore in ASP.NET MVC 2 con RedirectToAction

[HttpPost] 
public ActionResult Add(Thing thing) 
{ 
    // ... do validation, db stuff ... 
    return this.RedirectToAction<c => c.Details(thing.Id)); 
} 

Questa grande opera (utilizzando il RedirectToAction da l'assembly MvcContrib).

Quando eseguo il test dell'unità, desidero accedere al ViewData restituito dall'azione Dettagli (così posso ottenere la chiave primaria della cosa appena inserita e dimostrare che è ora nel database).

Il test ha:

var result = controller.Add(thing); 

Ma risultato qui è di tipo: System.Web.Mvc.RedirectToRouteResult (che è un System.Web.Mvc.ActionResult). Non ha ancora eseguito il metodo Dettagli.

Ho provato a chiamare ExecuteResult sull'oggetto restituito passando in un mocked up ControllerContext ma il framework non era soddisfatto della mancanza di dettagli nell'oggetto deriso.

Potrei provare a inserire i dettagli, ecc. Ecc., Ma il mio codice di test è molto più lungo del codice che sto testando e sento di aver bisogno di test unitari per i test unitari!

Mi manca qualcosa nella filosofia di testing? Come posso testare questa azione quando non riesco a raggiungere il suo stato restituito?

risposta

7

Sembra che tu stia facendo troppo per un test unitario. La convalida e l'accesso ai dati saranno in genere eseguiti dai servizi che chiamate dall'azione del controller. Fai schifo a quei servizi e verifica solo che siano stati chiamati correttamente.

Qualcosa di simile (utilizzando la sintassi approssimativo per Rhino.Mocks & NUnit):

[Test] 
public void Add_SavesThingToDB() 
{ 
    var dbMock = MockRepository.GenerateMock<DBService>(); 
    dbMock.Expect(x => x.Save(thing)).Repeat.Once(); 

    var controller = new MyController(dbMock); 
    controller.Add(new Thing()); 

    dbMock.VerifyAllExpectations(); 
} 

[Test] 
public void Add_RedirectsAfterSave() 
{ 
    var dbMock = MockRepository.GenerateMock<DBService>(); 

    var controller = new MyController(dbMock); 
    var result = (RedirectToRouteResult)controller.Add(new Thing()); 

    Assert.That(result.Url, Is.EqualTo("/mynew/url")); 
} 
+0

Grazie - che aiuta sicuramente ad evitare le difficoltà con il framework durante i test. Quindi, seguendo questo idioma avrei dei test unitari per il DBService che dimostrano che posso aggiungere cose e un test unitario per il controller che dimostra che le sue chiamate salvano sul servizio. Ma non ho davvero dimostrato che la cosa passata nel controller finisca nel database. Forse potrei farlo con un sacco di regole di derisione più complesse ... ma questo non sembra giusto, è un bel po 'di piastra di prova per una semplice operazione. –

+0

Beh, determinare lo scopo e lo sforzo da mettere nei test e su cosa testare, ecc., È qualcosa con cui ho difficoltà. Penso che dovresti provare a separare i test in due categorie: test unitari e test di integrazione. I test unitari dovrebbero testare solo unità di funzionalità molto piccole, come i test precedenti. I test di integrazione dovrebbero guardare a come tutto si integra, magari coprendo una piccola user story che hai. Preferirei fare i test di integrazione il più vicino possibile all'utilizzo "reale", ad esempio eseguendo WatiN e facendo effettivamente clic su un paio di collegamenti. Non c'è bisogno di prendere in giro lì affatto. – rmac

+0

Se testate il vostro DBService, dimostrerete che funziona. Quindi, si dovrebbe assumere che possa e gestirà correttamente le chiamate al database. Quindi se il tuo controller usa questo servizio, sai che funzionerà. Con il framework mock, è possibile convalidare i parametri che vengono passati ai metodi di servizio e questo è sufficiente. Penso che rmacfie abbia ragione, forse stai cercando di testare troppo più a fondo. Il test dell'unità dovrebbe coprire solo un'azione, non un intero processo. –

12

C'è MVC Contrib TestHelper che sono fantastici per testare la maggior parte del ActionResult

È possibile ottenere qui: http://mvccontrib.codeplex.com/wikipage?title=TestHelper

Ecco un esempio della sintassi:

var controller = new TestController(); 

controller.Add(thing) 
      .AssertActionRedirect() 
      .ToAction<TestController>(x => x.Index()); 

Per verificare se i dati sono stati mantenuti correttamente, dovresti forse chiedere direttamente al tuo database, non so se stai usando un ORM o qualcosa del genere, ma dovresti fare qualcosa per ottenere l'ultimo oggetto inserito nel tuo database, quindi confrontare con il valore che hai fornito al tuo Aggiungi ActionResult e vedere se questo è ok.

Non penso che testare i tuoi Dettagli ActionResult per vedere se i tuoi dati sono persistenti è l'approccio giusto. Quello non sarebbe un test unitario, più un test funzionale.

Ma dovresti anche testare unitamente il tuo metodo Details per assicurarti che l'oggetto viewdata sia riempito con i dati giusti provenienti dal tuo database.

31

Attualmente sto utilizzando MVC2 RC2 e la risposta di rmacfie non ha funzionato per me, ma mi ha portato sulla strada giusta.

A torto oa ragione sono riuscito a fare questo nel mio test, invece:

var actionResult = (RedirectToRouteResult)logonController.ForgotUsername(model); 

actionResult.RouteValues["action"].should_be_equal_to("Index"); 
actionResult.RouteValues["controller"].should_be_equal_to("Logon"); 

Non sono sicuro se questo aiuterà qualcuno, ma potrebbe risparmiare 10 minuti.

+2

Fantastico! È possibile controllare i valori del percorso aggiuntivo in modo simile. Per 'RedirectToAction (" Details "," Person ", {personId = 123})' puoi controllare 'personId':' Assert.AreEqual (123, actionResult.RouteValues ​​["personId"]) ' – Kirill

6

Ho un metodo di supporto statico che verifica il reindirizzamento.

public static class UnitTestHelpers 
{ 
    public static void ShouldEqual<T>(this T actualValue, T expectedValue) 
    { 
     Assert.AreEqual(expectedValue, actualValue); 
    } 

    public static void ShouldBeRedirectionTo(this ActionResult actionResult, object expectedRouteValues) 
    { 
     RouteValueDictionary actualValues = ((RedirectToRouteResult)actionResult).RouteValues; 
     var expectedValues = new RouteValueDictionary(expectedRouteValues); 

     foreach (string key in expectedValues.Keys) 
     { 
      Assert.AreEqual(expectedValues[key], actualValues[key]); 
     } 
    } 
} 

Quindi creare un test di reindirizzamento è molto semplice.

[Test] 
public void ResirectionTest() 
{ 
    var result = controller.Action(); 

    result.ShouldBeRedirectionTo(
     new 
     { 
      controller = "ControllerName", 
      action = "Index" 
     } 
    ); 
} 
+3

+1 per il metodo di estensione ma per qualche motivo il nome del controller MVC3 è nullo –

Problemi correlati