ASP.NET MVC v praxi od A do Z, 15. díl – Unit testy II. část
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu
Reklama
Reklama

ASP.NET MVC v praxi od A do Z, 15. díl – Unit testy II. částASP.NET MVC v praxi od A do Z, 15. díl – Unit testy II. část

 

ASP.NET MVC v praxi od A do Z, 15. díl – Unit testy II. část

Google       Google       10. 11. 2009       13 495×

V posledním dílu tohoto seriálu doděláme podporu pro unit testy, vytvoříme falešnou DinnerRepository třídu a napíšeme si pár dalších testů.

Reklama
Reklama

Navážeme na minulý díl a upravíme DinnersController tak, aby podporoval rozhraní IDinnerRepository namísto třídy DinnerRepository.

Momentálně vypadá třída DinnersController takto:

public class DinnersController : Controller
{
    DinnerRepository dinnerRepository = new DinnerRepository();

Upravená verze bude vypadat následovně:

public class DinnersController : Controller
{
    IDinnerRepository dinnerRepository;

    public DinnersController() : this(new DinnerRepository())
    {}

    public DinnersController(IDinnerRepository repository)
    {
        dinnerRepository = repository;
    }

Jak jistě vidíte, přidali jsme do třídy DinnersController dva konstruktory – první použije naši současnou implementaci třídy DinnerRepository, druhý umožňuje předat nějakou jinou třídu odvozenou od rozhraní IDinnerRepository.

Vytvoření třídy FakeDinnerRepository

Začneme vytvořením nového adresáře pojmenovaného „Fakes“ v projektu NerdDinner.Tests a rovnou do něj přidáme novou třídu FakeDinnerRepository.

Nová třída bude implementovat rozhraní IDinnerRepository, ukážu vám už rovnou finální kód:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NerdDinner.Models;

namespace NerdDinner.Tests.Fakes
{
    class FakeDinnerRepository : IDinnerRepository
    {
        private List<Dinner> dinnerList;

        public FakeDinnerRepository(List<Dinner> dinners)
        {
            dinnerList = dinners;
        }

        public IQueryable<Dinner> FindAllDinners()
        {
            return dinnerList.AsQueryable();
        }

        public IQueryable<Dinner> FindUpcomingDinners()
        {
            return (from dinner in dinnerList
                    where dinner.EventDate > DateTime.Now
                    select dinner).AsQueryable();
        }

        public Dinner GetDinner(int id)
        {
            return dinnerList.SingleOrDefault(d => d.DinnerID == id);
        }

        public IQueryable<Dinner> FindByLocation(float latitude, float longitude)
        {
            return (from dinner in dinnerList
                    where dinner.Latitude == latitude && dinner.Longitude == longitude
                    select dinner).AsQueryable();

        }

        public void Add(Dinner dinner)
        {
            dinnerList.Add(dinner);
        }

        public void Delete(Dinner dinner)
        {
            dinnerList.Remove(dinner);
        }

        public void Save()
        {
            foreach (Dinner dinner in dinnerList)
            {
                if (!dinner.IsValid)
                    throw new ApplicationException("Porušení validačních pravidel");
            }
        }
    }
}

Výborně, teď máme třídu implementující IDinnerRepository, která nepotřebuje připojení k databázi, ale pracuje jen s objekty v paměti.

Použití třídy FakedinnerRepository v unit testech

V minulém dílu jsme si napsali unit testy, které neprošly kvůli připojení k databázi, teď už to můžeme snadno napravit:

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NerdDinner.Controllers;
using NerdDinner.Models;
using NerdDinner.Tests.Fakes;

namespace NerdDinner.Tests.Controllers
{
    [TestClass]
    public class DinnersControllerTest
    {
        List<Dinner> CreateTestDinners()
        {
            List<Dinner> dinners = new List<Dinner>();

            for (int i = 0; i < 101; i++)
            {
                Dinner sampleDinner = new Dinner()
                {
                    DinnerID = i,
                    Title = "Nějaká večeře",
                    HostedBy = "Někdo",
                    Address = "Nějaká adresa",
                    Country = "USA",
                    ContactPhone = "425-555-1212",
                    Description = "Nějaký popis",
                    EventDate = DateTime.Now.AddDays(i),
                    Latitude = 99,
                    Longitude = -99
                };
                dinners.Add(sampleDinner);
            }

            return dinners;
        }

        DinnersController CreateDinnersController()
        {
            var repository = new FakeDinnerRepository(CreateTestDinners());
            return new DinnersController(repository);
        }


        [TestMethod]
        public void DetailsAction_Should_Return_View_For_Dinner()
        {
            // Vytvoření pokusného objektu
            var controller = CreateDinnersController();

            // Úkon
            var result = controller.Details(1);

            // Rozhodnutí o výsledku testu
            Assert.IsInstanceOfType(result, typeof(ViewResult));
        }

        [TestMethod]
        public void DetailsAction_Should_Return_NotFoundView_For_BogusDinner()
        {
            // Vytvoření pokusného objektu
            var controller = CreateDinnersController();

            // Úkon
            var result = controller.Details(999) as ViewResult;

            // Rozhodnutí o výsledku testu
            Assert.AreEqual("NotFound", result.ViewName);
        }
    }
}

Nyní by měly oba testy proběhnout bez problémů. Máme teď v rukou neskutečně silný nástroj pro testování DinnersControlleru – můžeme testovat jakoukoliv část aplikace používající tento controller bez přístupu k databázi, vše tak trvá jen pár sekund (ale testy si pořád musíme napsat sami).

Unit testy akční metody Edit

Teď je na řadě akční metoda Edit, nejprve její HTTP-GET verze:

[Authorize]
public ActionResult Edit(int id)
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");

    return View(new DinnerFormViewModel(dinner));
}

Vytvoříme test, který ověřuje, zda je view vrácený objektem DinnerFormViewModel vyrenderován, pokud zažádáme o validní večeři:

[TestMethod]
public void EditAction_Should_Return_View_For_ValidDinner()
{

    // Vytvoření pokusného objektu
    var controller = CreateDinnersController();

    // Úkon
    var result = controller.Edit(1) as ViewResult;

    // Rozhodnutí o výsledku testu
    Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
}

Tento test ale vrátí chybu. A to z poměrně jasného důvodu – dostaneme totiž výjimku null reference, když se metoda Edit pokusí přistoupit k vlastnosti User.Identity.Name pomocí metody Dinner.IsHostedBy().

Objekt User bázové třídy Controller zapouzdřuje detaily o přihlášeném uživateli. My ale testujeme DinnersController mimo webové prostředí a objektu User nejsou vůbec přiřazeny nějaké hodnoty (proto null reference). Proto si ho opět zfalšujeme.

Zfalšování vlastnosti User.Identity.Name

Za účelem vytváření „nepravých“ informací vznikly tzv. „mocking“ frameworky. Takový mocking framework nám může například dynamicky vytvářet objekt User, ze kterého pak budeme načítat uživatelské jméno.

Existuje hned několik mocking frameworků s podporou ASP.NET MVC, jejich výčet naleznete například zde. My využijeme framework nazvaný „Moq“, který stáhnete odsud.

Po stažení přidáme referenci na Moq.dll do projektu NerdDinner.Tests.

Teď nám už nic nebrání vytvořit pomocnou metodu, která přijme uživatelské jméno jako parametr a dosadí ho pomocí vlastnosti User.Identity.Name do instance DinnersControlleru:

// Nezapomeňte přidat direktivu using Moq; !
DinnersController CreateDinnersControllerAs(string userName)
{

    var mock = new Mock<ControllerContext>();
    mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
    mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);

    var controller = CreateDinnersController();
    controller.ControllerContext = mock.Object;

    return controller;
}

V kódu výše využíváme instanci třídy Mock pro vytvoření falešného ControllerContext objektu (to je objekt, který ASP.NET MVC předává všem Controller třídám a vystavuje objekty jako User, Request, Response a Session). Metodou SetupGet říkáme, že „userName“, který jsme předali metodě jako parametr, má být vrácen jako HttpContext.User.Identity.Name.

Podobným způsobem můžeme zfalšovat kolik vlastností a metod se nám jen zachce, například druhé volání metody SetupGet v kódu výše vrací „true“ pro autentifikaci uživatele.

Nyní můžeme vytvořit několik testů, které pro svou funkčnost vyžadují uživatelské jméno:

[TestMethod]
public void EditAction_Should_Return_EditView_When_ValidOwner()
{

    // Vytvoření pokusného objektu
    var controller = CreateDinnersControllerAs("Někdo");

    // Úkon
    var result = controller.Edit(1) as ViewResult;

    // Rozhodnutí o výsledku testu
    Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
}

[TestMethod]
public void EditAction_Should_Return_InvalidOwnerView_When_InvalidOwner()
{

    // Vytvoření pokusného objektu
    var controller = CreateDinnersControllerAs("Ne_vlastník");

    // Úkon
    var result = controller.Edit(1) as ViewResult;

    // Rozhodnutí o výsledku testu
    Assert.AreEqual(result.ViewName, "InvalidOwner");
} 

Ještě nesmíte zapomenout přepsat v testu EditAction_Should_Return_View_For_ValidDinner volání metody „CreateDinnersController“ na novou „CreateDinnersControllerAs“, která využívá mockingu. Všechny testy už by měly proběhnout v pořádku.

Edit ve verzi HTTP-POST

Testování GET verze metody Edit máme za sebou, na závěr celého seriálu se podíváme na její POST verzi. To, co dělá tuto situaci zajímavou, je přítomnost volání UpdateModel v metodě Edit:

// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Edit(int id, FormCollection formValues)
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");
    try
    {
        UpdateModel(dinner);
        dinnerRepository.Save();

        return RedirectToAction("Details", new {id = dinner.DinnerID});
    }
    catch
    {
        ModelState.AddRuleViolations(dinner.GetRuleViolations());

        return View(new DinnerFormViewModel(dinner));
    }
}

Níže vidíte dva testy, které ukazují způsob, jak metodě UpdateModel přiřadit informace, které má použít. Používáme k tomu objekt FormCollection, který potřebnými daty naplníme a pak ho přiřadíme do vlastnosti ValueProvider na controlleru.

První test ověřuje, zda je při úspěšném uložení uživatel přesměrován na akční metodu „Details“. Druhý test zase zjišťuje, zda je při neplatném vstupu znovu zobrazen view a chybová zpráva:

[TestMethod]
public void EditAction_Should_Redirect_When_Update_Successful()
{
    // Vytvoření pokusného objektu     
    var controller = CreateDinnersControllerAs("Někdo");

    var formValues = new FormCollection()
                         {
                             {"Title", "blabla"},
                             {"Description", "Nějaký popis"}
                         };

    controller.ValueProvider = formValues.ToValueProvider();

    // Úkon
    var result = controller.Edit(1, formValues) as RedirectToRouteResult;

    // Rozhodnutí o výsledku testu
    Assert.AreEqual("Details", result.RouteValues["Action"]);
}

[TestMethod]
public void EditAction_Should_Redisplay_With_Errors_When_Update_Fails()
{
    // Vytvoření pokusného objektu
    var controller = CreateDinnersControllerAs("Někdo");

    var formValues = new FormCollection()
                         {
                             {"EventDate", "(tady by mělo být datum, místo toho je tu tenhle text)"}
                         };

    controller.ValueProvider = formValues.ToValueProvider();

    // Úkon
    var result = controller.Edit(1, formValues) as ViewResult;

    // Rozhodnutí o výsledku testu
    Assert.IsNotNull(result, "Očekáváno znovuzobrazení view");
    Assert.IsTrue(result.ViewData.ModelState.Count > 0, "Očekávány chyby");
}

Závěr

A máme to za sebou! Tím myslím skutečně vše, co jsem vám chtěl v tomto seriálu sdělit. Nezbývá mi než doufat, že jste se naučili mnoho nových věcí a budete se této zajímavé technologii věnovat i nadále. Těším se na viděnou u dalšího seriálu.

Zdroj: http://nerddinnerbook.s3.amazonaws.com/Part12.htm

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

Hlasování bylo ukončeno    
0 hlasů
Google
(fotka) Jakub KottnauerJakub studuje informatiku na FIT ČVUT, jeho oblíbenou platformou je .NET.
Web     Twitter     Facebook     LinkedIn    

Nové články

Obrázek ke článku Delphi 10.1.2 (Berlin Update 2) – na co se můžeme těšit

Delphi 10.1.2 (Berlin Update 2) – na co se můžeme těšit

Touto roční dobou, kdy je zem pokrytá barevným listím a prsty křehnou v mrazivých ránech, se obvykle těšíme na zbrusu novou verzi RAD Studia. Letos si však ale budeme muset počkat na Godzillu a Linux až do jara. Vezměme tedy za vděk alespoň updatem 2 a jelikož dle vyjádření pánů z Embarcadero se budou nové věci objevovat průběžně, pojďme se na to tedy podívat.

Reklama
Reklama
Obrázek ke článku Konference: Moderní datová centra pro byznys dneška se koná už 24. 11.

Konference: Moderní datová centra pro byznys dneška se koná už 24. 11.

Stále rostoucí zájem o cloudové služby i maximální důraz na pružnost, spolehlivost a bezpečnost IT vedou k výrazným inovacím v datových centrech. V infrastruktuře datových center hraje stále významnější roli software a stále častěji se lze setkat s hybridními přístupy k jejich budování i provozu.

Obrázek ke článku Konference: Mobilní technologie mají velký potenciál pro byznys

Konference: Mobilní technologie mají velký potenciál pro byznys

Firmy by se podle analytiků společnosti Gartner měly  rychle přizpůsobit skutečnosti, že mobilní technologie už zdaleka nejsou horkou novinkou, ale standardní součástí byznysu. I přesto - nebo možná právě proto - tu nabízejí velký potenciál. Kde tedy jsou ty největší příležitosti? I tomu se bude věnovat již čtvrtý ročník úspěšné konference Mobilní řešení pro business.

Obrázek ke článku Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres HCPP16 pořádá od 30. září do 2. října nezisková organizace Paralelní Polis již potřetí, a to ve stejnojmenném bitcoinovém prostoru v pražských Holešovicích. Letos přiveze na třídenní konferenci přes 40 většinou zahraničních speakerů – lídrů z oblastí technologií, decentralizované ekonomiky, politických umění a aktivismu. Náměty jejich přednášek budou také hacking, kryptoměny, věda, svoboda nebo kryptoanarchie.

loadingtransparent (function() { var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })();
Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032016 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý