In the previous post, I gave an overview of how to get started with TDD. I tried to highlight the thought process and steps that I take when doing TDD. In this article I will continue building further on the shopping cart from the previous post and show how I can add further functionality to it using TDD. The tests that I want to focus in this article are the following:

  • Adding an item multiple times
  • Removing an item
  • Adding an item that is out of stock

Let’s Code

First I want to add tests for adding an item multiple times. Currently where we left of in the previous article, we do not cater for this scenario. Currently we are only returning the count of the items in the cart and there is no mechanism for storing the quantity of individual item. The tests that I will focus on now will make me implement this functionality. Let’s look at the code for the first test.

        [TestMethod]
        public void Add_AddingSameItemTwice_ItemQuantityShouldBeTwo()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            var item2 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
            cart.Add(item2);

            // Assert
            Assert.AreEqual(2, cart.Items[0].Quantity);
        }

The functionality that I want to test right now is that if I add the same item twice then the quantity of that item should be updated. Currently I do not have the Items property in my Cart class and my CartItem class does not contain the Quantity property so my code is not compiling. Let’s add these properties.

    public class CartItem
    {
        public string Sku { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
        public int Quantity { get; set; }
    }

    public class Cart
    {
        private readonly List<CartItem> _Items;

        public Cart()
        {
            _Items = new List<CartItem>();
        }

        public void Add(CartItem item)
        {
            _Items.Add(item);
        }

        public int Count
        {
            get { return _Items.Count; }
        }

        public double Total
        {
            get { return _Items.Sum(i => i.Price); }
        }

        public List<CartItem> Items {
            get
            {
                return _Items;

            }
        }
    }

if I run my test it fails. The expected value was 2 but the value for quantity is 0. Let’s make this test pass now. In my first attempt I will modify the logic in Add method to handle the quantity and update it if an item already exists.

        public void Add(CartItem item)
        {
            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);
            if (existingItem != null)
                existingItem.Quantity++;
            else
                _Items.Add(item);
        }

Now if I run my test, it still fails.

Failing Test

Item quantity is 1 instead of 2

The reason my quantity is invalid is because by default the value of quantity is 0. So when the first item is added, its quantity is 0. Next time when I add the same item twice, I update the quantity but as it was 0 before so it becomes only 1. To fix this I just need to set the default value of Quantity property to 1 in CartItem class constructor.

    public class CartItem
    {
        public CartItem()
        {
            Quantity = 1;
        }

        public string Sku { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
        public int Quantity { get; set; }
    }
All tests pass

All tests pass

At this point I have completed the Red and Green cycle of TDD, now we need to see if we need to refactor our code. Currently my code is clean and I do not see any problems. The only thought going through my mind is that I am repeating a lot of code in the arrange section of my tests. In the beginning there was always a temptation to create a common method and extract out this code. With experience however, I have learned that for unit tests, readability is a critical factor. If I just introduce a method for let’s say “CreateCartWithTwoIdenticalItems”, it will certainly look better in terms of DRY principal but any other person reading that code will first need to understand what is going on in those methods. It is extremely important with TDD that when someone read through your Unit Tests, they don’t need to think or assume anything. Ideally they should be able to look at the few lines of code in front of them and be very clear about your intention. Thus I am happy with my tests so far and I will not refactor out a method for the arrange sections. Now let’s add a test case to test the total amount in this case.

        [TestMethod]
        public void Add_AddingSameItemTwice_CartTotalShouldBeCorrect()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            var item2 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
            cart.Add(item2);

            // Assert
            Assert.AreEqual(2, cart.Total);
        }

If I run my test it fails.

Total is incorrect

Total is incorrect

Let’s fix the Total property and make our test pass.

        public double Total
        {
            get
            {
                double total = 0;
                foreach (var item in _Items)
                {
                    total += (item.Quantity * item.Price);
                }
                return total;
            }
        }

if I re-run all my tests, they pass 🙂

Now I need to refactor my code and I am not happy with my Total property so let’s refactor it. LINQ to the rescue!

        public double Total
        {
            get
            {
                return _Items.Sum(item => (item.Quantity*item.Price));
            }
        }

I re-run all my tests after refactoring and they all pass. Now there is one more scenario that I wan’t to test, so lets add the test for that

        [TestMethod]
        public void Add_AddingSameItemTwice_CartCountShouldBeOne()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            var item2 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
            cart.Add(item2);

            // Assert
            Assert.AreEqual(1, cart.Count);
        }

I run my test and it automatically passes. This in some cases can be a smell, you need to ask yourself did I write the correct test, am I testing a scenario which is invalid in a sense that it will always pass? In my case I know that this test is perfectly valid and it should pass, so I will move along and test the next feature.

Now let’s write a test for removing items from cart.

        [TestMethod]
        public void Remove_RemoveItemFromCartContainingOnlyThatItem_CartCountShouldBecomeZero()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Remove(item1);

            // Assert
            Assert.AreEqual(0, cart.Count);
        }

At this point my code does not compile as I do not have the Remove method, so lets add it and run the test, and surely it fails. Let’s make the test pass. Here is my Remove method in Cart class

        public void Remove(CartItem item)
        {
            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);
            if (existingItem != null)
                _Items.Remove(existingItem);
        }

My test passes, run all the tests to make sure that the all pass. I don’t see any big potential for refactoring right now so let’s add the next test.

        [TestMethod]
        public void Remove_RemoveItemFromCartContainingTwoQuantityOfThatItem_CartCountShouldBecome1()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            cart.Add(item1);
            cart.Add(item1);

            // Act
            cart.Remove(item1);

            // Assert
            Assert.AreEqual(1, cart.Count);
        }

If I run my test now it fails, let’s fix it.

        public void Remove(CartItem item)
        {
            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);
            if (existingItem != null)
            {
                if (existingItem.Quantity > 1)
                    existingItem.Quantity--;
                else
                _Items.Remove(existingItem);
            }
        }

Now all my tests pass, I see I can refactor my method to make it more readable. Here is the re-factored version

        public void Remove(CartItem item)
        {
            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);

            if (existingItem == null) return;

            if (existingItem.Quantity > 1)
                existingItem.Quantity--;
            else
                _Items.Remove(existingItem);
        }

Notice how I am just returning early if item does not exist. This makes it more readable and also I am returning early from my code.

The final scenario that I wan’t to cover is adding an item that is out of stock. In this scenario we will have a dependency on an external class to check the stock. To write a unit test we will need to mock out that class and that’s what I want to show you up next. The implementation that I want to go for is that whenever I want to add an item to stock, I will first call another class (ProductRepository) in my case to see if that product is in stock or not. If that product is in stock only then I will add it.

Before writing my test, I need to install Nuget Package for MOQ. There are many mocking frameworks available and they are all good but I have been using MOQ for some time now and have gotten used to it. So let’s install the MOQ package and write our next test.

Installing MOQ

Installing MOQ

The implementation that I am after is that if an item is out of stock, my Cart class will throw a custom exception of type “OutOfStockException”. I will create a simpler interface for IProductRepository and the OutOfStockException class first.

    public class OutOfStockException : Exception
    {
    }

    public interface IProductRepository
    {
        bool IsProductInStock(string sku);
    }

Here is my test.

        [TestMethod]
        [ExpectedException(typeof(OutOfStockException))]
        public void Add_AddingAnItemThatIsOutOfStock_ShouldThrowException()
        {
            // Arrange
            var mock = new Mock<IProductRepository>();
            var cart =; new Cart(mock.Object);
            mock.Setup(x => x.IsProductInStock(It.IsAny<string>())).Returns(false);

            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
        }

You can see that in line number 2 I am specifying that I am expecting an exception of type OutOfStockException to be raised as a result of this test. If this exception is not raised, my test fails. This is the syntax for asserting exceptions in MS Test, other testing frameworks might have different syntax. Next I am creating a mock of my IProductRepository interface in line # 6. I need to pass that mock to my Cart class as it now will depend on an implementation of IProductRepository to check if item is in stock or not? Finally in line # 8, I am setting up the behaviour of my mock, I am saying that when the IsProductInStock method is called with any string value, it should always return false. Thus I am simulating the behaviour that when I check to see if item is in stock, it will return false.

At this stage if I try to build my code, it will fail because now our Cart class does not take an instance of IProductRepository in it’s constructor. Let’s change the code to fix that.

    public class Cart
    {
        private readonly IProductRepository _productRepository;

        public Cart(IProductRepository productRepository)
        {
            _productRepository = productRepository;
            _Items = new List<CartItem>();
        }

        private readonly List<CartItem> _Items;

        public void Add(CartItem item)
        {
            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);
            if (existingItem != null)
                existingItem.Quantity++;
            else
                _Items.Add(item);
        }

        public int Count
        {
            get { return _Items.Count; }
        }

        public double Total
        {
            get { return _Items.Sum(item => (item.Quantity * item.Price)); }
        }

        public List<CartItem> Items
        {
            get { return _Items; }
        }

        public void Remove(CartItem item)
        {
            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);

            if (existingItem == null) return;

            if (existingItem.Quantity > 1)
                existingItem.Quantity--;
            else
                _Items.Remove(existingItem);
        }
    }

My code will still not build because my initialization code in the CreateEmptyCart method is not passing the instance of IproductRepository now. To fix this problem I will just create a class level field for the mock of IProductRepository and utilize that in my CreateEmptyCart method.

    public class CartTests
    {
        private readonly Mock<IProductRepository> _mock = new Mock<IProductRepository>();
        private Cart CreateEmptyCart()
        {
            _mock.Setup(x => x.IsProductInStock(It.IsAny<string>())).Returns(true);
            return new Cart(_mock.Object);
        };

        .
        .  // Rest of the tests here
        .

        [TestMethod]
        [ExpectedException(typeof(OutOfStockException))]
        public void Add_AddingAnItemThatIsOutOfStock_ShouldThrowException()
        {
            // Arrange
            var cart = new Cart(_mock.Object);
            _mock.Setup(x => x.IsProductInStock(It.IsAny<string>())).Returns(false);

            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
        }

    }

I have now moved the mock field from my test at class level and utilize that in the CreateEmptyCart method. Since I had already refactored the creation of empty cart in a separate method, I did not need to go and change all my tests, I told you it would help 🙂 Now if I run my tests; all tests pass except my last test and I need to make it pass.

        public void Add(CartItem item)
        {
            if (!_productRepository.IsProductInStock(item.Sku))
                throw new OutOfStockException();

            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);
            if (existingItem != null)
                existingItem.Quantity++;
            else
                _Items.Add(item);
        }

I am simply calling the IsProductInStock method of ProductRepository to check if item is out of stock. If it is not in stock I am just throwing my custom exception. Now lets run our test to see if it passes; it does, re-run all the tests and make sure they all pass. I am happy with the current state of my code and don’t see any major refactoring required. Here is my complete finalized code.

    public class OutOfStockException : Exception
    {
    }
    public interface IProductRepository
    {
        bool IsProductInStock(string sku);
    }
    public class CartItem
    {
        public CartItem()
        {
            Quantity = 1;
        }

        public string Sku { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
        public int Quantity { get; set; }
    }
    public class Cart
    {
        private readonly IProductRepository _productRepository;

        public Cart(IProductRepository productRepository)
        {
            _productRepository = productRepository;
            _Items = new List<CartItem>();
        }

        private readonly List<CartItem> _Items;

        public void Add(CartItem item)
        {
            if (!_productRepository.IsProductInStock(item.Sku))
                throw new OutOfStockException();

            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);
            if (existingItem != null)
                existingItem.Quantity++;
            else
                _Items.Add(item);
        }

        public int Count
        {
            get { return _Items.Count; }
        }

        public double Total
        {
            get { return _Items.Sum(item => (item.Quantity * item.Price)); }
        }

        public List<CartItem> Items
        {
            get { return _Items; }
        }

        public void Remove(CartItem item)
        {
            var existingItem = _Items.FirstOrDefault(i => i.Sku == item.Sku);

            if (existingItem == null) return;

            if (existingItem.Quantity > 1)
                existingItem.Quantity--;
            else
                _Items.Remove(existingItem);
        }
    }
    public class CartTests
    {
        private readonly Mock<IProductRepository> _mock = new Mock<IProductRepository>();
        private Cart CreateEmptyCart()
        {
            _mock.Setup(x => x.IsProductInStock(It.IsAny<string>())).Returns(true);
            return new Cart(_mock.Object);
        }

        [TestMethod]
        public void Add_AddingItemToEmptyCart_CountShouldBeOne()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item);

            // Assert
            Assert.AreEqual(1, cart.Count);
        }

        [TestMethod]
        public void Add_AddingItemToEmptyCart_TotalShouldBeThePriceOfTheItem()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item);

            // Assert
            Assert.AreEqual(1, cart.Total);
        }

        [TestMethod]
        public void Add_AddingTwoItemsToEmptyCart_CountShouldBeTwo()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            var item2 = new CartItem { Sku = "456", Name = "Fresh Juice", Price = 1.00 };

            // Act
            cart.Add(item1);
            cart.Add(item2);

            // Assert
            Assert.AreEqual(2, cart.Count);
        }

        [TestMethod]
        public void Add_AddingTwoItemsToEmptyCart_TotalShouldBeTheCorrect()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            var item2 = new CartItem { Sku = "456", Name = "Fresh Juice", Price = 2.00 };

            // Act
            cart.Add(item1);
            cart.Add(item2);

            // Assert
            Assert.AreEqual(3, cart.Total);
        }

        [TestMethod]
        public void Add_AddingSameItemTwice_ItemQuantityShouldBeTwo()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            var item2 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
            cart.Add(item2);

            // Assert
            Assert.AreEqual(2, cart.Items[0].Quantity);
        }

        [TestMethod]
        public void Add_AddingSameItemTwice_CartTotalShouldBeCorrect()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            var item2 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
            cart.Add(item2);

            // Assert
            Assert.AreEqual(2, cart.Total);
        }

        [TestMethod]
        public void Add_AddingSameItemTwice_CartCountShouldBeOne()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            var item2 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
            cart.Add(item2);

            // Assert
            Assert.AreEqual(1, cart.Count);
        }

        [TestMethod]
        public void Remove_RemoveItemFromCartContainingOnlyThatItem_CartCountShouldBecomeZero()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Remove(item1);

            // Assert
            Assert.AreEqual(0, cart.Count);
        }

        [TestMethod]
        public void Remove_RemoveItemFromCartContainingTwoQuantityOfThatItem_CartCountShouldBecome1()
        {
            // Arrange
            var cart = CreateEmptyCart();
            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };
            cart.Add(item1);
            cart.Add(item1);

            // Act
            cart.Remove(item1);

            // Assert
            Assert.AreEqual(1, cart.Count);
            ;
        }

        [TestMethod]
        [ExpectedException(typeof(OutOfStockException))]
        public void Add_AddingAnItemThatIsOutOfStock_ShouldThrowException()
        {
            // Arrange
            var cart = new Cart(_mock.Object);
            _mock.Setup(x => x.IsProductInStock(It.IsAny<string>())).Returns(false);

            var item1 = new CartItem { Sku = "123", Name = "Softdrink", Price = 1.00 };

            // Act
            cart.Add(item1);
        }

    }

Summary

In this article I continued on the previous one and showed you how you can get started with TDD. I started with very simple tests but ended up with a class that has quite a lot of functionality. During the process I showed you how you can use mocking to mock out external dependencies. You can build upon this code and add further functionality now. Hopefully you would have found answers to some common questions and confusions that beginners have when starting TDD. Do check out my other tutorials on the site regarding TDD and also what mocking is all about. Till next time Happy Coding!