Listing 3-1. The C# Auction Domain Model
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public class Member { public string LoginName { get; set; } // The unique key public int ReputationPoints { get; set; } } public class Bid { public Member Member { get; set; } public DateTime DatePlaced { get; set; } public decimal BidAmount { get; set; } } public class Item { public int ItemID { get; private set; } // The unique key public string Title { get; set; } public string Description { get; set; } public DateTime AuctionEndDate { get; set; } public IList<Bid> Bids { get; set; } } }
Listing 3-2. C# Repository Classes for the Member and Item Domain Classes
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public class MembersRepository { public void AddMember(Member member) { } public Member FetchByLoginName(string loginName) { return null; } public void SubmitChanges() { } } public class ItemsRepository { public void AddItem(Item item) { } public Item FetchById(int itemId) { return null; } public IList<Item> ListItems(int pageSize, int pageIndex) { return null; } public void SubmitChanges() { } } }
Listing 3-3. Instantiating Concrete Classes to Get an Interface Implementation
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public class PasswordResetHelper { public void ResetPassword() { IEmailSender mySender = new MyEmailSender(); // Call interface methods to configure e-mail details mySender.SendEmail(); } } } using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public interface IEmailSender { void SendEmail(); } public class MyEmailSender : IEmailSender { public void SendEmail() { } } }
Listing 3-4. Removing Dependencies from the PasswordResetHelper Class
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public class PasswordResetHelper { private IEmailSender _emailSender; public PasswordResetHelper(IEmailSender emailSender) { _emailSender = emailSender; } public void ResetPassword() { // Call interface methods to configure e-mail details _emailSender.SendEmail(); } } }
Listing 3-5. The IMembersRepository Interface
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public interface IMembersRepository { void AddMember(Member member); Member FetchByLoginName(string loginName); void SubmitChanges(); } public class MembersRepository : IMembersRepository { public void AddMember(Member member) { } public Member FetchByLoginName(string loginName) { return null; } public void SubmitChanges() { } } }
Listing 3-6. The AdminController Class
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using MvcPattern.Models; namespace MvcPattern.Controllers { public class AdminController : Controller { IMembersRepository _membersRepository; public AdminController(IMembersRepository membersRepository) { _membersRepository = membersRepository; } public ActionResult ChangeLoginName(string oldLoginName, string newLoginName) { Member member = _membersRepository.FetchByLoginName(oldLoginName); member.LoginName = newLoginName; _membersRepository.SubmitChanges(); return View(); } } }
Listing 3-7. An Example Test Fixture
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MvcPattern.Controllers; using MvcPattern.Models; namespace MvcPattern.Tests { [TestClass] public class AdminControllerTest { [TestMethod] public void CanChangeLoginName() { // Arrange (set up a scenario) Member bob = new Member() { LoginName = "Bob" }; FakeMembersRepository repository = new FakeMembersRepository(); repository.Members.Add(bob); AdminController target = new AdminController(repository); string oldLoginName = bob.LoginName; string newLoginName = "Anastasia"; // Act (attempt the operation) target.ChangeLoginName(oldLoginName, newLoginName); // Assert (verify the result) Assert.AreEqual(newLoginName, bob.LoginName); Assert.IsTrue(repository.DidSubmitChanges); } } private class FakeMembersRepository : IMembersRepository { public List<Member> Members = new List<Member>(); public bool DidSubmitChanges = false; public void AddMember(Member member) { } public Member FetchByLoginName(string loginName) { return Members.First(m => m.LoginName == loginName); } public void SubmitChanges() { DidSubmitChanges = true; } } }
Listing 3-8. Adding a Stub Method to the Item Class
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public class Item { public int ItemID { get; private set; } // The unique key public string Title { get; set; } public string Description { get; set; } public DateTime AuctionEndDate { get; set; } public IList<Bid> Bids { get; set; } public void AddBid(Member member, Decimal amount) { throw new NotImplementedException(); } } }
Listing 3-9. Three Test Fixtures
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MvcPattern.Controllers; using MvcPattern.Models; namespace MvcPattern.Tests { [TestClass] public class AdminControllerTest { [TestMethod] public void CanAddBid() { // Arrange - set up the scenario Item target = new Item(); Member member = new Member(); Decimal amount = 150M; // Act - perform the test target.AddBid(member, amount); // Assert - check the behavior Assert.AreEqual(1, target.Bids.Count()); Assert.AreEqual(amount, target.Bids[0].BidAmount); } [TestMethod()] [ExpectedException(typeof(InvalidOperationException))] public void CannotAddLowerBid() { // Arrange Item target = new Item(); Member member = new Member(); Decimal amountParam = 150M; // Act target.AddBid(member, amountParam); target.AddBid(member, amountParam - 10); } [TestMethod()] public void CanAddHigherBid() { // Arrange Item target = new Item(); Member firstMember = new Member(); Member secondMember = new Member(); Decimal amount = 150M; // Act target.AddBid(firstMember, amount); target.AddBid(secondMember, amount + 10); // Assert Assert.AreEqual(2, target.Bids.Count()); Assert.AreEqual(amount + 10, target.Bids[1].BidAmount); } } }
Listing 3-10. Implementing the AddBid Method
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public class Item { public int ItemID { get; private set; } // The unique key public string Title { get; set; } public string Description { get; set; } public DateTime AuctionEndDate { get; set; } public IList<Bid> Bids { get; set; } public Item() { Bids = new List<Bid>(); } public void AddBid(Member member, Decimal amount) { Bids.Add(new Bid() { BidAmount = amount, DatePlaced = DateTime.Now, Member = member }); } } }
Listing 3-11. Improving the Implementation of the AddBid Method
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcPattern.Models { public class Item { public int ItemID { get; private set; } // The unique key public string Title { get; set; } public string Description { get; set; } public DateTime AuctionEndDate { get; set; } public IList<Bid> Bids { get; set; } public Item() { Bids = new List<Bid>(); } public void AddBid(Member member, Decimal amount) { if (Bids.Count == 0 || amount > Bids.Max(e => e.BidAmount)) { Bids.Add(new Bid() { BidAmount = amount, DatePlaced = DateTime.Now, Member = member }); } else { throw new InvalidOperationException("Bid amount too low"); } } } }