2015 Ignite New Zealand demo prep: Step 7

Steps:

  1. Intro
  2. VSO
  3. Docker
  4. xUnit
  5. Build
  6. Back end
  7. Selenium
  8. Docker
  9. Release Management
  10. Testing

In this post, we are going to complete the test project we created in the previous posts.  We are going to use Selenium as the test framework to allow us to test the UI during our release.

Before we can test our application, we need it to actually do something. In the post where we created all the Docker hosts, we only had a “Hello World” ASP.NET 5 application.  We created the backend in the last post.  In this post we will create the functionality and create the tests.  This is a very long post so, to speed you up, I created a repo on GitHub with each step of this blog series.

To begin we are going to add a test row to our database. Then we are going to deploy our Web API into the Azure Web Apps we created previously.  Finally, we will complete the ASP.NET 5 application.  Please note I will not be going into great detail on how ASP.NET 5 works; this post is long enough as it is.

1.       Set Id column of Database to Identity

a.       Right-click the dbo.People table in SQL Server Object Explorer and select View Designer

b.       Right-click the Id roll and select Properties

c.       Change Identity Specification to True

d.       Click Update

e.       Click Update Database

2.       Add test row

a.       Right-click the dbo.People table in SQL Server Object Explorer and select View Data

b.       Enter a Test User row

3.       Publish your Web API to Azure

a.       Open the PeopleTrackerWebService solution

b.       Right-click PeopleTracker.NetService and select Publish

c.       Select Microsoft Azure Web Apps

d.       Select the Web App we created for our dev environment

e.       Click OK

f.        Click Next

g.       Select Release for Configuration

h.       Verify connection string is correct

i.        Check the check box to use this connection string at runtime

j.        Click Next

k.       Click Publish

                                                    i.     Once the publish is complete a browser will open with our Web API loaded.

                                                   ii.     Copy the URL and paste it somewhere you can get to. You are going to need this URL later.

4.       Test Web API

a.       In the browser that opened after you published the Web API project append api/people to the address

b.       A json file will be returned with our test user in it.

With the Web API deployed, we can now finish our front end.  Buckle up boys and girls because this is going to get crazy.  If you do not want to copy and paste the code from this post, you can clone this repo instead. You will want to switch to branch Step7.  What we are about to do is write all the code that will implement the CRUD operations for our person object.  Instead of accessing the database directly we are going to use the Web API that we just deployed.  We are going to define an interface and use the repository pattern to hide the details from our controller class so we can switch the data access layer in the future.  Because we have multiple environments, we have to have a way to configure the URL for the Web API for each host.  We are going to use the new Options feature of ASP.NET5 for this purpose.

5.       Add Options Class

a.       Open PeopleTracker Solution

b.       Right-click on the PeopleTracker.Preview project and select Add / Class…

c.       Name the file SiteOptions.cs

d.       Replace the contents of the file with the code below

namespace PeopleTracker.Preview

{

   public class SiteOptions

   {

      /// <summary>

      /// Stores the URL of the Web API

      /// </summary>

      public string WebApiBaseUrl { get; set; }

 

      /// <summary>

      /// Stores the build number running in this image

      /// </summary>

      public string BuildNumber { get; set; }

   }

}

6.       Add Person Model

a.       Create a Models folder in the PeopleTracker.Preview project

b.       Right-click on the Models folder and select Add / Class…

c.       Name the file Person.cs

d.       Replace the contents of the file with the codeu below

namespace PeopleTracker.Preview.Models

{

   using System.ComponentModel.DataAnnotations;

 

   public class Person

   {

      public int ID { get; set; }

 

      [Required]

      public string FirstName { get; set; }

 

      [Required]

      public string LastName { get; set; }

   }

}

7.       Add Repository Interface

a.       Right-click on the Models folder and select Add / New Item…

b.       Select Interface and name the file IRepository.cs

c.       Replace the contents of the file with the code below

namespace PeopleTracker.Preview.Models

{

   using System.Collections.Generic;

 

   public interface IRepository

   {

      IEnumerable<Person> People { get; }

      bool AddPerson(Person person);

      void RemovePerson(Person person);

      bool UpdatePerson(Person person);

   }

}

8.       Add References to System.Net.Http, Microsoft.Net.Http, and Microsoft.AspNet.WebApi.Client

a.       Right-click PeopleTracker.Preview project and select Manage NuGet Packages…

b.       Uncheck the Include prerelease checkbox

c.       Add the following packages to your project

·    System.net.http

·    Microsoft.AspNet.WebApi.Client

·    Microsoft.Net.Http

b.       Open project.json

c.       Move the Microsoft.AspNet.WebApi.Client and Microsoft.Net.Http from dnx451 section to the root dependencies section

9.       Add Repository Class

a.       Right-click on the Models folder and select Add / Class…

b.       Name the file Repository.cs

c.       Replace the contents of the file with the code below

namespace PeopleTracker.Preview.Models

{

   using Microsoft.Framework.OptionsModel;

   using System;

   using System.Collections.Generic;

   using System.Net.Http;

   using System.Net.Http.Headers;

 

   public class Repository : IRepository

   {

      private IOptions<SiteOptions > siteOptions;

 

      public Repository( IOptions<SiteOptions> options)

      {

         this .siteOptions = options;

      }

 

      public IEnumerable<Person > People

      {

         get

         {

            var client = GetClient();

            var response = client.GetAsync( "api/People").Result;

 

            if (response.IsSuccessStatusCode)

            {

               return response.Content.ReadAsAsync< IEnumerable<Person>>().Result;

            }

 

            return new Person[0];

         }

      }

 

      public bool AddPerson(Person person)

      {

         var client = GetClient();

         var response = client.PostAsJsonAsync< Person>("api/People", person).Result;

 

         return response.IsSuccessStatusCode;

      }

 

      public void RemovePerson(Person person)

      {

         var client = GetClient();

 

         // If you don't wait you will return to the list page before the item

         // is removed.

         client.DeleteAsync("api/People/" + person.ID).Wait();

      }

 

      public bool UpdatePerson(Person person)

      {

         var client = GetClient();

         var response = client.PutAsJsonAsync< Person>("api/People/" + person.ID, person).Result;

 

         return response.IsSuccessStatusCode;

      }

 

      private HttpClient GetClient()

      {

         HttpClient client = new HttpClient();

         client.BaseAddress = new Uri(siteOptions.Value.WebApiBaseUrl);

 

         // Add an Accept header for JSON format.

         client.DefaultRequestHeaders.Accept.Add(

             new MediaTypeWithQualityHeaderValue("application/json"));

 

         return client;

      }

   }

}

 

10.    Add People Controller to PeopleTracker.Preview

a.       Right-click on the Controllers folder and select Add / Class…

b.       Name the file PeopleController.cs

c.       Replace the contents of the file with the code below

namespace PeopleTracker.Preview.Controllers

{

   using Microsoft.AspNet.Mvc;

   using Microsoft.Framework.OptionsModel;

   using PeopleTracker.Preview.Models;

   using System.Linq;

   using System.Net;

 

   public class PeopleController : Controller

   {

      private IRepository db;

      private IOptions<SiteOptions > siteOptions;

 

      public PeopleController( IRepository repo, IOptions<SiteOptions > options)

      {

         this .db = repo;

         this .siteOptions = options;

      }

 

      public IActionResult Create()

      {

         ViewData["WebApiBaseUrl" ] = this.siteOptions?.Value.WebApiBaseUrl;

 

         return this.View();

      }

 

      [HttpPost]

      [ValidateAntiForgeryToken]

      public ActionResult Create([Bind("ID", "FirstName" , "LastName")] Person person)

      {

         ViewData["WebApiBaseUrl" ] = this.siteOptions.Value.WebApiBaseUrl;

 

         if (this.ModelState.IsValid)

         {

            this.db.AddPerson(person);

            return this.RedirectToAction("Index");

         }

 

         return View(person);

      }

 

      public ActionResult Delete(int ? id)

      {

         ViewData["WebApiBaseUrl" ] = this.siteOptions.Value.WebApiBaseUrl;

 

         if (id == null)

         {

            return new HttpStatusCodeResult((int)HttpStatusCode.BadRequest);

         }

 

         var person = this .db.People.First(p => p.ID == id);

         if (person == null)

         {

            return this.HttpNotFound();

         }

 

         return View(person);

      }

 

      [HttpPost]

      [ActionName("Delete")]

      [ValidateAntiForgeryToken]

      public ActionResult DeleteConfirmed(int id)

      {

         ViewData["WebApiBaseUrl" ] = this.siteOptions.Value.WebApiBaseUrl;

 

         var person = this .db.People.FirstOrDefault(p => p.ID == id);

         if (person == null)

         {

            return this.HttpNotFound();

         }

         this.db.RemovePerson(person);

         return this.RedirectToAction("Index");

      }

 

      public ActionResult Details(int ? id)

      {

         ViewData["WebApiBaseUrl" ] = this.siteOptions.Value.WebApiBaseUrl;

 

         if (id == null)

         {

            return new HttpStatusCodeResult((int)HttpStatusCode.BadRequest);

         }

         var person = this .db.People.FirstOrDefault(p => p.ID == id);

         if (person == null)

         {

            return this.HttpNotFound();

         }

         return View(person);

      }

 

      public ActionResult Edit(int ? id)

      {

         ViewData["WebApiBaseUrl" ] = this.siteOptions.Value.WebApiBaseUrl;

 

         if (id == null)

         {

            return new HttpStatusCodeResult((int)HttpStatusCode.BadRequest);

         }

         var person = this .db.People.FirstOrDefault(p => p.ID == id);

         if (person == null)

         {

            return this.HttpNotFound();

         }

         return View(person);

      }

 

      [HttpPost]

      [ValidateAntiForgeryToken]

      public ActionResult Edit([Bind("ID", "FirstName" , "LastName")] Person person)

      {

         ViewData["WebApiBaseUrl" ] = this.siteOptions.Value.WebApiBaseUrl;

 

         if (this.ModelState.IsValid)

         {

            this.db.UpdatePerson(person);

            return this.RedirectToAction("Index");

         }

         return View(person);

      }

 

      public IActionResult Index()

      {

         ViewData["WebApiBaseUrl" ] = this.siteOptions.Value.WebApiBaseUrl;

 

         return this.View(db.People.ToList());

      }

   }

}

 

11.    Add the Create People View

a.       Add a People folder in the Views folder

b.       Right-click the People folder and select Add / New Item

c.       Select MVC View Page

d.       Name the file Create.cshtml

e.       Replace the contents of the file with the code below

@model PeopleTracker.Preview.Models.Person

 

@{

   ViewBag.Title = "Create";

}

 

<h2>Create</h2>

 

<form asp-controller="People" asp-action="Create" method="post">

   <div class="form-horizontal">

      <h4>Person</h4>

      <hr />

      <div asp-validation-summary="ValidationSummary.All"></div>

 

      <div class="form-group">

         <label asp-for="FirstName" class ="control-label col-md-2"></ label>

         <div class="col-md-10">

            <input asp-for="FirstName" />

            <span asp-validation-for="FirstName"></span>

         </div>

      </div>

      <div class="form-group">

         <label asp-for="LastName" class ="control-label col-md-2"></ label>

         <div class="col-md-10">

            <input asp-for="LastName" />

            <span asp-validation-for="LastName"></span>

         </div>

      </div>

 

      <div class="form-group">

         <div class="col-md-offset-2 col-md-10">

            <input type="submit" value="Create" class="btn btn-default" />

         </div>

      </div>

   </div>

</form>

 

<div>

   <a asp-action="Index">Back to List</a>

</div>

 

12.    Add the Delete People View

a.       Add a People folder in the Views folder

b.       Right-click the People folder and select Add / New Item

c.       Select MVC View Page

d.       Name the file Delete.cshtml

e.       Replace the contents of the file with the code below

@model PeopleTracker.Preview.Models.Person

 

@{

   ViewBag.Title = "Delete";

}

 

<h2>Delete</h2>

 

<h3> Are you sure you want to delete this? </h3>

<div>

   <h4>Person</h4>

   <hr />

   <dl class="dl-horizontal">

      <dt><label asp-for="FirstName"></label></dt>

      <dd>@Model.FirstName</dd>

      <dt><label asp-for="LastName"></label></dt>

      <dd>@Model.LastName</dd>

   </dl>

 

   <form asp-controller="People" asp-action="Delete" method="post" asp-route-id="@Model.ID">

      <div class="form-actions no-color">

         <button type="submit" class ="btn btn-default"> Delete</button> |

         <a asp-action="Index">Back to List</a>

      </div>

   </form>

</div>

 

13.    Add the Details People View

a.       Add a People folder in the Views folder

b.       Right-click the People folder and select Add / New Item

c.       Select MVC View Page

d.       Name the file Details.cshtml

e.       Replace the contents of the file with the code below

@model PeopleTracker.Preview.Models.Person

 

@{

   ViewBag.Title = "Details";

}

 

<h2>Details</h2>

 

<div>

   <h4>Person</h4>

   <hr />

   <dl class="dl-horizontal">

      <dt><label asp-for="LastName"></label></dt>

      <dd>@Model.FirstName</dd>

      <dt><label asp-for="FirstName"></label></dt>

      <dd>@Model.LastName</dd>

   </dl>

</div>

<p>   

   <a asp-action="Edit" asp-route-id="@Model.ID" asp-controller="People">Edit</a>

   |

   <a asp-action="Index">Back to List</a>

</p>

 

14.    Add the Edit People View

a.       Add a People folder in the Views folder

b.       Right-click the People folder and select Add / New Item

c.       Select MVC View Page

d.       Name the file Edit.cshtml

e.       Replace the contents of the file with the code below

@model PeopleTracker.Preview.Models.Person

 

@{

   ViewBag.Title = "Edit";

}

 

<h2>Edit</h2>

 

<form asp-controller="People" asp-action="Edit" method="post" asp-route-id="@Model.ID">

   <div class="form-horizontal">

      <h4>Person</h4>

      <hr />

      <div asp-validation-summary="ValidationSummary.All"></div>

      <input type="hidden" asp-for="ID" />

 

      <div class="form-group">

         <label asp-for="FirstName" class ="control-label col-md-2"></ label>

         <div class="col-md-10">

            <input asp-for="FirstName" />

            <span asp-validation-for="FirstName"></span>

         </div>

      </div>

      <div class="form-group">

         <label asp-for="LastName" class ="control-label col-md-2"></ label>

         <div class="col-md-10">

            <input asp-for="LastName" />

            <span asp-validation-for="LastName"></span>

         </div>

      </div>

 

      <div class="form-group">

         <div class="col-md-offset-2 col-md-10">

            <input type="submit" value="Save" class="btn btn-default" />

         </div>

      </div>

   </div>

</form>

 

<div>

   <a asp-action="Index">Back to List</a>

</div>

 

15.    Add the Index People View

a.       Add a People folder in the Views folder

b.       Right-click the People folder and select Add / New Item

c.       Select MVC View Page

d.       Name the file Index.cshtml

e.       Replace the contents of the file with the code below

@model IEnumerable<PeopleTracker.Preview.Models.Person>

 

@{

   ViewBag.Title = "People";

}

 

<h2>Index</h2>

 

<p>

   <a asp-controller="People" asp-action="Create">Create New</a>

</p>

<table class="table">

   <tr>

      <th>

         @ Html.DisplayNameFor(model => model.FirstName)

      </th>

      <th>

         @ Html.DisplayNameFor(model => model.LastName)

      </th>

      <th></th>

   </tr>

 

   @foreach (var item in Model)

   {

      <tr>

         <td>

            @item.FirstName

         </td>

         <td>

            @item.LastName

         </td>

         <td>

            <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |

            <a asp-action="Details" asp-route-id="@item.ID">Details</a> |

            <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>

         </td>

      </tr>

   }

 

</table>

 

16.    Add People to Main Menu

a.       Open _Layout.cshtml

b.       Add the following code to the navbar-nav ul

<li><a asp-controller="People" asp-action="Index">People</a></li>

17.    Configure Services

a.       Open Startup.cs

b.       Copy and paste this code below services.AddMvc(); in ConfigureServices

services.AddOptions();

services.Configure<SiteOptions>(Configuration);

services.AddScoped<IRepository, Repository>();

c.       Use the light bulb to add any required usings

 

18.    Update appsettings.json

Depending on when you started following this series, you may have a config.json or an appsettings.json.  The goal is to add values to the file configured with the ConfigurationBuilder in the Startup class.

a.       Open appsettings.json (or config.json depending on your version of ASP.NET5)

b.       Replace the contents of the file with the code below

{

   "WebApiBaseUrl" : "http://localhost:52593/",

   "BuildNumber" : "1"

}

c.       Update the base url to point to your dev instance of the Web API

19.    Update copyright with base url and build number

a.       Open HomeController.cs

b.       Replace the contents of the file with the code below

namespace PeopleTracker.Preview.Controllers

{

   using Microsoft.AspNet.Mvc;

   using Microsoft.Framework.OptionsModel;

  

   public class HomeController : Controller

   {

      private IOptions<SiteOptions > siteOptions;

 

      public HomeController( IOptions<SiteOptions> options)

      {

         this .siteOptions = options;

      }

 

      public IActionResult Index()

      {

         SetCopyright();

 

         return View();

      }

 

      private void SetCopyright()

      {

         ViewData["WebApiBaseUrl" ] = string.Format( "{0} - build: {1}" , this .siteOptions.Value.WebApiBaseUrl, this.siteOptions.Value.BuildNumber);

      }

 

      public IActionResult About()

      {

         ViewData["Message" ] = "People Tracker is a demo application that shows the power of Microsoft DevOps." ;

         SetCopyright();

 

         return View();

      }

 

      public IActionResult Contact()

      {

         ViewData["Message" ] = "Follow me on Twitter to stay connected to Microsoft DevOps." ;

         SetCopyright();

 

         return View();

      }

 

      public IActionResult Error()

      {

         SetCopyright();

         return View( "~/Views/Shared/Error.cshtml");

      }

   }

}

 

20.    Update _Layout.cshtml to show build and base url

a.       Open _Layout.cshtml

b.       Replace your copyright paragraph with the code below

<p>&copy; 2015 - PeopleTracker.Preview - @ViewData["WebApiBaseUrl"]</p>

Adding the dependency on site options to the HomeController class will break our unit tests we added earlier.  We need to update the tests so they will build and pass.

21.    Add FakeOptions class

a.       Right-click on PeopleTracker.Preview.Tests project and select Add / Class

b.       Name the file FakeOptions.cs

c.       Replace the contents of the file with the code below

namespace PeopleTracker.Preview.Tests

{

   using Microsoft.Framework.OptionsModel;

 

   public class FakeOptions : IOptions<SiteOptions>

   {

      private SiteOptions _value;

 

      public FakeOptions( SiteOptions value)

      {

         _value = value;

      }

 

      public SiteOptions Value

      {

         get

         {

            return _value;

         }

      }

   }

}

22.    Update unit test

a.       Open HomeControllerTests.cs

b.       Replace the contents of the file with the code below

namespace PeopleTracker.Preview.Tests

{

   using Xunit;

 

   public class HomeControllerTests

   {

      [Fact]

      public void Index()

      {

         // Arrange

         var target = new PeopleTracker.Preview.Controllers.HomeController(new FakeOptions(new SiteOptions()));

 

         // Act

         var results = target.Index();

 

         // Assert

         Assert.NotNull(results);

      }

   }

}

 

At this point, you should have a fully functional ASP.NET 5 People Tracker web site.  Now we can use this web site to record our Selenium tests.

23.    Add Selenium references

a.       Open PeopleTrackerWebService Solution

b.       Right-click on your UI test project and select Manage NuGet Packages…

c.       Add the following packages to your project

·    Selenium.WebDriver

·    Selenium.Support

·    Selenium.WebDriver.ChromeDriver

·    Selenium.WebDriver.IEDriver

I installed only Chrome and IE drivers because the Firefox driver is part of the core framework and does not have to be installed separately.  The drivers are how I select the browser I want to use to execute my test.

The tests have to be written in such a way that we do not hardcode the URL of the site being tested. This way we can pass in the desired URL during test execution during our release.  We are also going to use the Test Category attribute so we can categorize Unit tests and UI tests. This will allow us to only run UI tests during our release.

24.    Write test case

a.       Open UnitTest1.cs

b.       Replace the contents of the file with the code below

namespace PeopleTracker.UITests

{

   using Microsoft.VisualStudio.TestTools.UnitTesting;

   using OpenQA.Selenium;

   using OpenQA.Selenium.Chrome;

   using OpenQA.Selenium.Firefox;

   using OpenQA.Selenium.IE;

   using OpenQA.Selenium.Remote;

   using System;

 

   [TestClass]

   public class UnitTest1

   {

      private string baseURL;

      private RemoteWebDriver driver;

      private string browser;

      public TestContext TestContext { get ; set; }

 

      [TestMethod]

      [TestCategory("UI")]

      public void AddPerson()

      {

         driver.Manage().Window.Maximize();

         driver.Manage().Timeouts().ImplicitlyWait( TimeSpan.FromSeconds(30));

 

         driver.Navigate().GoToUrl(this.baseURL);

         driver.FindElementByLinkText("People").Click();

         driver.FindElementByLinkText("Create New").Click();

         driver.FindElementById("FirstName").Clear();

         driver.FindElementById("FirstName").SendKeys(browser);

         driver.FindElementById("LastName").Clear();

         driver.FindElementById("LastName").SendKeys("User");

         driver.FindElementByCssSelector("input.btn").Click();

 

         // Force chrome to slow down so the click is registered

         var wait = new OpenQA.Selenium.Support.UI.WebDriverWait(driver, TimeSpan.FromSeconds(30));

         wait.Until(driver1 => ((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete"));

      }

      /// <summary>

      /// Use TestCleanup to run code after each test has run

      /// </summary>

      [TestCleanup()]

      public void MyTestCleanup()

      {

         driver.Quit();

      }

 

      [TestInitialize()]

      public void MyTestInitialize()

      {

         browser = this.TestContext.Properties["browser"] != null ? this.TestContext.Properties["browser"].ToString() : "ie";

 

         switch (browser)

         {

            case "firefox":

               driver = new FirefoxDriver();

               break;

 

            case "chrome":

               driver = new ChromeDriver();

               break;

 

            default:

               driver = new InternetExplorerDriver();

               break;

         }

 

         // To generate code for this test, select "Generate Code for Coded UI Test" from the shortcut menu and select one of the menu items.

         if (this.TestContext.Properties["webAppUrl" ] != null)

         {

            this .baseURL = this.TestContext.Properties["webAppUrl"].ToString();

         }

         else

         {

            this .baseURL = "http://{yourDockerHostName}.cloudapp.azure.com/";

         }

      }

   }

}

 

Replace {yourDockerHostName} with your Docker host name.

25.    Install Chrome on your VM

26.    Install Firefox on your VM

27.    Update your build to only run Unit Test during your build

a.       Edit your build in VSTS

b.       Click the Visual Studio Test task

c.       Change the Test Filter criteria to

TestCategory=Unit

Making this change to your build will make sure it does not try to run your UI test during your build. We only want to run the UI tests during our release which we will configure in a future post.

28.    Add TestCategory attribute to PeopleTracker.NetServices.Tests

a.       Open HomeControllerTest.cs

b.       Replace the contents of the file with the code below

namespace PeopleTracker.NetService.Tests.Controllers

{

   using Microsoft.VisualStudio.TestTools.UnitTesting;

   using PeopleTracker.NetService.Controllers;

   using System.Web.Mvc;

 

   [TestClass]

   public class HomeControllerTest

   {

      [TestMethod]

      [TestCategory("Unit")]

      public void Index()

      {

         // Arrange

         HomeController controller = new HomeController();

 

         // Act

         ViewResult result = controller.Index() as ViewResult;

 

         // Assert

         Assert.IsNotNull(result);

         Assert.AreEqual("Home Page", result.ViewBag.Title);

      }

   }

}

29.    Commit and push your changes

a.       From the View menu select Team Explorer

b.       Click Changes button

c.       Enter a comment

d.       Select Commit and Push

Committing your changes will trigger the build we configured in a previous step.  Verify that the build succeeds.

Pingbacks and trackbacks (16)+

Add comment

Loading