0% found this document useful (0 votes)
32 views50 pages

Chapter 14 Slides

Uploaded by

aisha.adediran0
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
32 views50 pages

Chapter 14 Slides

Uploaded by

aisha.adediran0
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 50

Chapter 14

How to use
dependency injection
and unit testing

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 1
Objectives (part 1)
Applied
1. Use dependency injection (DI) to make the code for an ASP.NET
Core web app more flexible and easier to change.
2. Use unit testing to automate the testing of a web app.

Knowledge
3. Describe how to configure a web app for dependency injection.
4. List and describe the three dependency life cycles.
5. Distinguish between controllers that are tightly coupled with EF
Core and controllers that are loosely coupled with EF Core.
6. Explain how dependency chaining works with repository objects
and unit of work objects.
7. Describe the use of DI with an HttpContextAccessor object.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 2
Objectives (part 2)
6. Describe the use of DI with an action method that uses the
FromServices attribute.
7. Describe the use of DI to inject an object into a view.
8. Explain how unit testing works.
9. Describe three advantages of unit testing.
10. Explain how to add an xUnit project to a solution.
11. Describe the Arrange/Act/Assert (AAA) pattern that’s often used
to organize the code for a unit test.
12. Explain how to run unit tests.
13. Describe the use of a fake object to test methods that have
dependencies.
14. Describe the use of Moq to create fake objects.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 3
Three methods of the IServiceCollection object
that map dependencies
AddTransient<interface, class>()
AddScoped<interface, class>()
AddSingleton<interface, class>()

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 4
How to configure DI for a class
that doesn’t use generics
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<IBookstoreUnitOfWork,
BookstoreUnitOfWork>();
...
}

How to configure DI for a generic class


services.AddTransient(typeof(IRepository<>),
typeof(Repository<>));

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 5
How to configure DI
for the HTTP context accessor
Manually
services.AddSingleton<IHttpContextAccessor,
HttpContextAccessor>();

With a method of the IServiceCollection object


services.AddHttpContextAccessor();

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 6
An Author controller
That injects a DbContext object
public class AuthorController : Controller
{
private Repository<Author> data { get; set; }

public AuthorController(BookstoreContext ctx) =>


data = new Repository<Author>(ctx);
...
}

That injects a repository object


public class AuthorController : Controller
{
private IRepository<Author> data { get; set; }

public AuthorController(IRepository<Author> rep) =>


data = rep;
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 7
A Book controller
That injects a DbContext object
public class BookController : Controller
{
private BookstoreUnitOfWork data { get; set; }

public BookController(BookstoreContext ctx) =>


data = new BookstoreUnitOfWork(ctx);
...
}

That injects a unit of work object


public class BookController : Controller
{
private IBookstoreUnitOfWork data { get; set; }

public BookController(IBookstoreUnitOfWork unit) =>


data = unit;
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 8
A Cart class that injects
an HttpContextAccessor object
...
using Microsoft.AspNetCore.Http;
...
public class Cart {
...
private ISession session { get; set; }
private IRequestCookieCollection requestCookies { get; set; }
private IResponseCookies responseCookies { get; set; }

public Cart(IHttpContextAccessor ctx) {


session = ctx.HttpContext.Session;
requestCookies = ctx.HttpContext.Request.Cookies;
responseCookies = ctx.HttpContext.Response.Cookies;
}
...
public void Load(IRepository<Book> data) {
// code that uses the repository and HttpContext
// to load cart items
}
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 9
A Cart controller that injects
an HttpContextAccessor object (part 1)
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Bookstore.Models;
...
public class CartController : Controller {
private IRepository<Book> data { get; set; }
private IHttpContextAccessor accessor { get; set; }
private Cart cart { get; set; }

public CartController(IRepository<Book> rep,


IHttpContextAccessor http)
{
data = rep;
accessor = http;
cart = new Cart(accessor);
cart.Load(data);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 10
A Cart controller that injects
an HttpContextAccessor object (part 2)
public ViewResult Index()
{
var builder =
new BooksGridBuilder(accessor.HttpContext.Session);
// rest of action method code
}
...
[HttpPost]
public RedirectToActionResult Edit(CartItem item) {
cart.Edit(item); // no need to create its own Cart object
cart.Save();
// rest of action method code
}
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 11
A controller that doesn’t use DI
using Microsoft.AspNetCore.Mvc;
using Bookstore.Models;
...
public class ValidationController : Controller
{
private Repository<Author> authorData { get; set; }
private Repository<Genre> genreData { get; set; }

public ValidationController(BookstoreContext ctx) {


authorData = new Repository<Author>(ctx);
genreData = new Repository<Genre>(ctx);
}
public JsonResult CheckGenre(string genreId) {
validate.CheckGenre(genreId, genreData);
...
}
public JsonResult CheckAuthor(string firstName, string lastname,
string operation) {
validate.CheckAuthor(firstName, lastName, operation,
authorData);
...
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 12
The same controller with DI in its action methods
using Microsoft.AspNetCore.Mvc;
using Bookstore.Models;
...
public class ValidationController : Controller
{
// private properties and constructor no longer needed

public JsonResult CheckGenre(


string genreId, [FromServices]IRepository<Genre> data) {
validate.CheckGenre(genreId, data);
...
}

public JsonResult CheckAuthor(string firstName, string lastName,


string operation, [FromServices] IRepository<Author> data) {
validate.CheckAuthor(firstName, lastName, operation, data);
...
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 13
Code in a layout that doesn’t inject a Cart object
@{
var cart = new Cart(Context);
...
}

A navigation link that uses the Cart object


<a class="nav-link" asp-action="Index"
asp-controller="Cart" asp-area="">
<span class="fas fa-shopping-cart"></span>&nbsp;Cart
<span class="badge badge-light">@cart.Count</span>
</a>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 14
How to set up the Cart object for DI
The ICart interface
public interface ICart {
// declarations for properties and methods of the Cart class
}

The Cart class updated to implement the interface


public class Cart : ICart {
// properties and methods that now implement the interface
}

The dependency mapping in the Startup.cs file


public void ConfigureServices(IServiceCollection services) {
...
// other dependency mappings
services.AddTransient<ICart, Cart>();
...
}

Code in a layout that injects a Cart object


@inject ICart cart

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 15
The unit testing process

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 16
Advantages of unit testing
 Unit testing reduces the amount of time you have to spend
manually testing an app by running it and entering data.
 Unit testing makes it easy to test an app after each significant
code change. This helps you find problems earlier in the
development cycle than you typically would when using manual
testing.
 Unit testing makes debugging easier because you typically test
an app after each significant code change. As a result, when a test
fails, you only need to debug the most recent code changes.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 17
The Add New Project dialog

How to add a unit test project to a solution


1. Right-click the solution and select AddNew Project.
2. In the Add New Project dialog, select the xUnit Test Project
(.NET Core) template and click Next. To help find the
template, you can use the dialog to search for “xunit”.
3. Enter a name and location for the project and click Create.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 18
The web app project and the unit test project

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 19
Some static methods of the Assert class
Equal(expected, result)
NotEqual(expected, result)
False(Boolean)
True(Boolean)
IsType<T>(result)
IsNull(result)

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 20
Three attributes of the Xunit namespace
Fact
Theory
InlineData(p1, p2, ...)

The using directive for the Xunit namespace


using Xunit;

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 21
A test class with two test methods (part 1)
public class NavTests
{
[Fact]
public void ActiveMethod_ReturnsAString()
{
string s1 = "Home"; // arrange
string s2 = "Books";

var result = Nav.Active(s1, s2); // act

Assert.IsType<string>(result); // assert
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 22
A test class with two test methods (part 2)
[Theory]
[InlineData("Home", "Home")]
[InlineData("Books", "Books")]
public void ActiveMethod_ReturnsValueActiveIfMatch(
string s1, string s2)
{
string expected = "active"; // arrange

var result = Nav.Active(s1, s2); // act

Assert.Equal(expected, result); // assert


}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 23
The Test Explorer in Visual Studio

Two ways to open the Test Explorer


 From the menu system, select TestTest Explorer.
 In the Solution Explorer, right-click on the test class and select
Run Tests.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 24
Some options to run tests in Test Explorer

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 25
A controller that depends on a repository class
public class HomeController : Controller
{
private IRepository<Book> data { get; set; }
public HomeController(IRepository<Book> rep) => data = rep;

public ViewResult Index() {


var random = data.Get(new QueryOptions<Book> {
OrderBy = b => Guid.NewGuid()
});
return View(random);
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 26
A fake repository class that implements
the Get() method
using Bookstore.Models;
...
public class FakeBookRepository : IRepository<Book>
{
public int Count =>
throw new NotImplementedException();
public void Delete(Book entity) =>
throw new NotImplementedException();
public Book Get(QueryOptions<Book> options) =>
new Book();
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 27
A unit test that passes an instance
of the fake repository to the controller
[Fact]
public void IndexActionMethod_ReturnsAViewResult() {
// arrange
var rep = new FakeBookRepository();
var controller = new HomeController(rep);

// act
var result = controller.Index();

// assert
Assert.IsType<ViewResult>(result);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 28
An action method that accesses TempData
[HttpPost]
public IActionResult Edit(Author author)
{
if (ModelState.IsValid) {
data.Update(author);
data.Save();
TempData["message"] = $"{author.FullName} updated.";
return RedirectToAction("Index");
}
else {
return View("Author", author);
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 29
The FakeTempData class with an indexer
that does nothing
using Microsoft.AspNetCore.Mvc.ViewFeatures;
...
public class FakeTempData : ITempDataDictionary
{
public object this[string key] { get => null; set { } }
public ICollection<string> Keys =>
throw new NotImplementedException();
...
}

Two methods of the FakeAuthorRepository class


that do nothing
public void Update(Author entity) { }
public void Save() { }

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 30
A test method that tests the action method
that uses TempData
[Fact]
public void Edit_POST_ReturnsRedirectToActionResultIfModelStateIsValid() {
// arrange
var rep = new FakeAuthorRepository();
var controller = new AuthorController(rep) {
TempData = new FakeTempData()
};

// act
var result = controller.Edit(new Author());

// assert
Assert.IsType<RedirectToActionResult>(result);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 31
How to add Moq to your test project
1. In the Solution Explorer, right-click the test project and select
Manage NuGet Packages from the resulting menu.
2. In the NuGet Package Manager, click Browse, search for
“moq”, select the Moq package, and click Install.
3. In the resulting dialogs, click OK and I Accept.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 32
Two methods of the Mock<T> class
Setup(lambda)
Returns(value)

One property of the Mock<T> class


Object

Two static methods of the It class


IsAny<T>()
Is<T>(lambda)

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 33
The using directive for the Moq namespace
using Moq;

Code that creates a Mock<IRepository<Author>>


object
var rep = new Mock<IRepository<Author>>();

Code that sets up Get() to accept any int


and return an Author object
rep.Setup(m => m.Get(It.IsAny<int>())).Returns(new Author());

The same statement adjusted to accept


any int greater than zero
rep.Setup(m => m.Get(It.Is<int>(i => i > 0))).Returns(
new Author());

Code that passes the mock object to a controller


var controller = new AuthorController(rep.Object);

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 34
A test method that mocks a repository object
using Bookstore.Controllers;
...
[Fact]
public void IndexActionMethod_ReturnsViewResult()
{
// arrange
var rep = new Mock<IRepository<Book>>();
var controller = new HomeController(rep.Object);

// act
var result = controller.Index();

// assert
Assert.IsType<ViewResult>(result);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 35
A test method that mocks a TempData object
using Bookstore.Areas.Admin.Controllers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
...
[Fact]
public void Edit_POST_ReturnsRedirectToActionResultIfModelStateIsValid()
{
// arrange
var rep = new Mock<IRepository<Author>>();
var temp = new Mock<ITempDataDictionary>();
var controller = new AuthorController(rep.Object)
{
TempData = temp.Object
};

// act
var result = controller.Edit(new Author());

// assert
Assert.IsType<RedirectToActionResult>(result);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 36
The constructor of the Cart class
public Cart(IHttpContextAccessor ctx)
{
// assign private variables
session = ctx.HttpContext.Session;
requestCookies = ctx.HttpContext.Request.Cookies;
responseCookies = ctx.HttpContext.Response.Cookies;
items = new List<CartItem>();
}

The Subtotal property of the Cart class


public double Subtotal => items.Sum(i => i.Subtotal);

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 37
A test method that tests the Subtotal property
of the Cart with Moq (part 1)
...
using Microsoft.AspNetCore.Http;
...
[Fact]
public void SubtotalProperty_ReturnsADouble()
{
// arrange
var accessor = new Mock<IHttpContextAccessor>();
var context = new DefaultHttpContext();

accessor.Setup(m => m.HttpContext).Returns(context);


accessor.Setup(m => m.HttpContext.Request)
.Returns(context.Request);
accessor.Setup(m => m.HttpContext.Response)
.Returns(context.Response);
accessor.Setup(m => m.HttpContext.Request.Cookies)
.Returns(context.Request.Cookies);
accessor.Setup(m => m.HttpContext.Response.Cookies)
.Returns(context.Response.Cookies);

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 38
A test method that tests the Subtotal property
of the Cart with Moq (part 2)
var session = new Mock<ISession>();
accessor.Setup(m => m.HttpContext.Session)
.Returns(session.Object);

Cart cart = new Cart(accessor.Object);


cart.Add(new CartItem { Book = new BookDTO() });

// act
var result = cart.Subtotal;

// assert
Assert.IsType<double>(result);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 39
The Test Explorer for the Bookstore.Tests project

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 40
The BookControllerTests class (part 1)
using Microsoft.AspNetCore.Mvc;
using Xunit;
using Moq;
using Bookstore.Controllers;
using Bookstore.Models;

namespace Bookstore.Tests
{
public class BookControllerTests
{
[Fact]
public void Index_ReturnsARedirectToActionResult() {
// arrange
var unit = new Mock<IBookstoreUnitOfWork>();
var controller = new BookController(unit.Object);

// act
var result = controller.Index();

// assert
Assert.IsType<RedirectToActionResult>(result);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 41
The BookControllerTests class (part 2)
[Fact]
public void Index_RedirectsToListActionMethod() {
// arrange
var unit = new Mock<IBookstoreUnitOfWork>();
var controller = new BookController(unit.Object);

// act
var result = controller.Index();

// assert
Assert.Equal("List", result.ActionName);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 42
The BookControllerTests class (part 3)
[Fact]
public void Details_ModelIsABookObject() {
// arrange
var bookRep = new Mock<IRepository<Book>>();
bookRep.Setup(m => m.Get(
It.IsAny<QueryOptions<Book>>()))
.Returns(new Book { BookAuthors =
new List<BookAuthor>() });
var unit = new Mock<IBookstoreUnitOfWork>();
unit.Setup(m => m.Books).Returns(bookRep.Object);

var controller = new BookController(unit.Object);

// act
var model =
controller.Details(1).ViewData.Model as Book;

// assert
Assert.IsType<Book>(model);
}
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 43
The AdminBookControllerTests class (part 1)
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Xunit;
using Moq;
using Bookstore.Areas.Admin.Controllers;
using Bookstore.Models;

namespace Bookstore.Tests
{
public class AdminBookControllerTests
{
public IBookstoreUnitOfWork GetUnitOfWork()
{
// set up Book repository
var bookRep = new Mock<IRepository<Book>>();
bookRep.Setup(m => m.Get(It.IsAny<QueryOptions<Book>>()))
.Returns(new Book { BookAuthors = new List<BookAuthor>() });
bookRep.Setup(m => m.List(It.IsAny<QueryOptions<Book>>()))
.Returns(new List<Book>());
bookRep.Setup(m => m.Count).Returns(0);

// set up Author repository


var authorRep = new Mock<IRepository<Author>>();
authorRep.Setup(m => m.List(It.IsAny<QueryOptions<Author>>()))
.Returns(new List<Author>());

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 44
The AdminBookControllerTests class (part 2)
// set up Genre repository
var genreRep = new Mock<IRepository<Genre>>();
genreRep.Setup(m => m.List(It.IsAny<QueryOptions<Genre>>()))
.Returns(new List<Genre>());

// set up unit of work


var unit = new Mock<IBookstoreUnitOfWork>();
unit.Setup(m => m.Books).Returns(bookRep.Object);
unit.Setup(m => m.Authors).Returns(authorRep.Object);
unit.Setup(m => m.Genres).Returns(genreRep.Object);

return unit.Object;
}

[Fact]
public void Edit_GET_ModelIsBookObject()
{
// arrange
var unit = GetUnitOfWork();
var controller = new BookController(unit);

// act
var model = controller.Edit(1).ViewData.Model as BookViewModel;

// assert
Assert.IsType<BookViewModel>(model);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 45
The AdminBookControllerTests class (part 3)
[Fact]
public void Edit_POST_ReturnsViewResultIfModelIsNotValid()
{
// arrange
var unit = GetUnitOfWork();
var controller = new BookController(unit);
controller.ModelState.AddModelError("", "Test error message.");
BookViewModel vm = new BookViewModel();

// act
var result = controller.Edit(vm);

// assert
Assert.IsType<ViewResult>(result);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 46
The AdminBookControllerTests class (part 4)
[Fact]
public void Edit_POST_ReturnsRedirectToActionResultIfModelIsValid()
{
// arrange
var unit = GetUnitOfWork();
var controller = new BookController(unit);
var temp = new Mock<ITempDataDictionary>();
controller.TempData = temp.Object;
BookViewModel vm = new BookViewModel { Book = new Book() };

// act
var result = controller.Edit(vm);

// assert
Assert.IsType<RedirectToActionResult>(result);
}
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 47
The CartTests class (part 1)
using System;
using System.Linq;
using Xunit;
using Moq;
using Microsoft.AspNetCore.Http;
using Bookstore.Models;

namespace Bookstore.Tests
{
public class CartTests
{
private Cart GetCart()
{
// create HTTP context accessor
var accessor = new Mock<IHttpContextAccessor>();

// setup request and response cookies


var context = new DefaultHttpContext();
accessor.Setup(m => m.HttpContext)
.Returns(context);
accessor.Setup(m => m.HttpContext.Request)
.Returns(context.Request);
accessor.Setup(m => m.HttpContext.Response)
.Returns(context.Response);

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 48
The CartTests class (part 2)
accessor.Setup(m => m.HttpContext.Request.Cookies)
.Returns(context.Request.Cookies);
accessor.Setup(m => m.HttpContext.Response.Cookies)
.Returns(context.Response.Cookies);

// setup session
var session = new Mock<ISession>();
accessor.Setup(m => m.HttpContext.Session)
.Returns(session.Object);

return new Cart(accessor.Object);


}

[Fact]
public void Subtotal_ReturnsADouble()
{
// arrange
Cart cart = GetCart();
cart.Add(new CartItem { Book = new BookDTO() });

// act
var result = cart.Subtotal;

// assert
Assert.IsType<double>(result);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 49
The CartTests class (part 3)
[Theory]
[InlineData(9.99, 6.89, 12.99)]
[InlineData(8.97, 45.00, 9.99, 15.00)]
public void Subtotal_ReturnsCorrectCalculation(
params double[] prices)
{
// arrange
Cart cart = GetCart();
for (int i = 0; i < prices.Length; i++)
{
var item = new CartItem
{
Book = new BookDTO { BookId = i, Price = prices[i] },
Quantity = 1
};
cart.Add(item);
}
double expected = prices.Sum();

// act
var result = cart.Subtotal;

// assert
Assert.Equal(Math.Round(expected, 2), Math.Round(result, 2));
}
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C14, Slide 50

You might also like