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

 
Hledat
Vybavení pro Laser Game
Laser Game Praha
Pokemon Go - vše o nové hře
Harry Potter a prokleté dítě

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

Google       Google       10. 11. 2009       13 845×

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 JIC otevírá největší digitální dílnu pro veřejnost v České republice

JIC otevírá největší digitální dílnu pro veřejnost v České republice

JIC otevírá první nonstop veřejně dostupnou digitální dílnu světového formátu s vybavením za 3 miliony korun. Dílnu může využívat po registraci kdokoliv. V  prostorách vzniknou prototypy produktů místních startupů, projekty kutilů a studentů i umělecká díla. Cílem dílny je zpřístupnit veřejnosti drahé přístroje a přitáhnout více podnikavých lidí k technickým oborům.

Reklama
Reklama
Obrázek ke článku Nový IT hráč na českém trhu

Nový IT hráč na českém trhu

V roce 2015 otevřela v Praze na Pankráci v budově City Tower své kanceláře společnost EPAM Systems (NYSE:EPAM), jejíž centrála se nachází v USA. Společnost byla založená v roce 1993 a od té doby prošla velkým vývojem a stále roste.

Obrázek ke článku České Radiokomunikace opět hledají nejlepší nápady pro internet věcí

České Radiokomunikace opět hledají nejlepší nápady pro internet věcí

České Radiokomunikace (CRA) pořádají druhý ročník CRA IoT Hackathonů. Zájemci z řad vývojářů a fanoušků moderních technologií mohou změřit své síly a během jediného dne sestrojit co nejzajímavější funkční prototyp zařízení, které bude komunikovat prostřednictvím sítě LoRa. CRA IoT Hackathony se letos uskuteční ve dvou fázích, na jaře a na podzim, v různých městech České republiky. Jarní běh se odstartuje 31. března v Brně a 7. dubna v Praze.

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 © 20032017 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý