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

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

 
Hledat
Vybavení pro Laser Game
Spuštěn Filmový magazín
Laser Game Brno
Pergoly a střechy Brno

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

Google       Google       10. 11. 2009       16 249×

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
Jakub studuje informatiku na FIT ČVUT, jeho oblíbenou platformou je .NET.
Web     Twitter     Facebook     LinkedIn    

Nové články

Obrázek ke článku Konference: Moderní informační systémy podporují automatizaci

Konference: Moderní informační systémy podporují automatizaci

Současná situace v šíření onemocnění Covid-19 klade na řadu firem nové nároky a mnohé z nich jsou nyní více než kdy jindy závislé na nejmodernějších informačních technologiích. Proto i v oblasti podnikových informačních systémů vidíme rostoucí důraz na automatizaci nebo na důslednou integraci. Také o těchto trendech se bude mluvit na konferenci Firemní informační systémy, která se koná 24.9.2020 v pražském Kongresovém centru Vavruška na Karlově náměstí.

Reklama
Reklama
Obrázek ke článku Nebezpečí ukrytá v USB: z nuly na škvarek za pět sekund

Nebezpečí ukrytá v USB: z nuly na škvarek za pět sekund

Za cenu šesti dolarů lze celkem bez obtíží koupit nový, líbivě vyhlížející flash disk. Přidaná hodnota, které se vám spolu s ním dostane, už tak moc líbivá není. To, co se před pár sekundami tvářilo jako externí disk, se po připojení k počítači změní v důmyslné elektrické křeslo, které vaše zařízení v onen příslovečný škvarek promění za pár sekund. Cílovou skupinou pro koupi takových zařízení by mohli být záškodníci, kteří by tímto způsobem osnovali pomstu třeba vůči záletnému partnerovi. 

Obrázek ke článku Znalosti, dovednosti i prestižní titul MBA: Jde to i moderně a online

Znalosti, dovednosti i prestižní titul MBA: Jde to i moderně a online

Snad nikdy není špatná příležitost na investici do hodnotného vzdělání. Obzvlášť v případě, že absolvent dovede teoretické poznatky přetavit v praktické dovednosti, využitelné při řešení problémů i v komunikaci. Právě na to se specializuje studijní program MBA Řízení informačních technologií, vyučovaný na Business Institutu.

Obrázek ke článku Coding Bootcamp Praha: Obor IT krize nepoznamenala, žádaní jsou weboví vývojáři

Coding Bootcamp Praha: Obor IT krize nepoznamenala, žádaní jsou weboví vývojáři

Pandemie Covid-19 otřásla trhem práce v základech. Dopady krize pocítilo celkově až 45 % zaměstnanců. Není divu, že čím dál větší jistotu přináší obor IT. Ten zůstal krizí téměř nepoznamenán a při nutnosti začít dělat věci na dálku se ještě více ukázalo, jak moc mnohé firmy kvalitní IT potřebují. Do IT nyní přicházejí začátečníci, kteří v něm vidí lukrativní budoucnost a jistotu, ale i freelanceři a zaměstnanci z oborů zasažených krizí

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032020 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý