TOTD Unit Testing cartHelper

TOTD stands for think of the developer and I feel its something that the commerce team should have as an objective. As a company, we are new to Episerver Commerce but it seems that we have to do way too much work to unit test our commerce code. This article will describe how we went about unit testing update quantity cart code. So lets look at the code to update the quantity of an item in the basket

<pre class="Plum_Code_Box"><code class="csharp">// Copyright 2015 Green Man Gaming

var lineItem = CartHelper
                .LineItems
                .FirstOrDefault(l =&gt; l.CatalogEntryId == skuId);

if (lineItem == null)
    return;

lineItem.Quantity = quantity;  
CartHelper.AcceptChanges();
</code>

ok, so we need to mock CartHelper, but its a static method, so we need to write a cart wrapper and inject that into the class we want to test.

<pre class="Plum_Code_Box"><code class="csharp">// Copyright 2015 Green Man Gaming

using System.Collections.Generic;
using System.Linq;

using Mediachase.Commerce.Catalog.Objects;
using Mediachase.Commerce.Orders;
using Mediachase.Commerce.Website.Helpers;

namespace GMGShop.Domain.Wrappers
{
    /// &lt;summary&gt;
    /// Cart Helper Wrapper so that we can test
    /// &lt;/summary&gt;
    public class CartHelperWrapper : ICartHelperWrapper
    {
        private CartHelper cartHelper;

        /// &lt;summary&gt;
        /// Gets a value indicating whether this instance is empty.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// &lt;c&gt;true&lt;/c&gt; if this instance is empty; otherwise, &lt;c&gt;false&lt;/c&gt;.
        /// &lt;/value&gt;
        public bool IsEmpty
        {
            get
            {
                return cartHelper.IsEmpty;
            }
        }

        /// &lt;summary&gt;
        /// Gets the total.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The total.
        /// &lt;/value&gt;
        public decimal Total
        {
            get
            {
                return cartHelper.Cart.Total;
            }
        }

        /// &lt;summary&gt;
        /// Sets the provider id.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The provider id.
        /// &lt;/value&gt;
        public string ProviderId
        {
            set
            {
                cartHelper.Cart.ProviderId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets the line items.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The line items.
        /// &lt;/value&gt;
        public IEnumerable&lt;ILineItemWrapper&gt; LineItems
        {
            get
            {
                return cartHelper
                    .LineItems
                    .Select(lineItem =&gt; new LineItemWrapper(lineItem));
            }
        }

        /// &lt;summary&gt;
        /// Initializes a new instance of the &lt;see cref="T:Mediachase.Commerce.Website.Helpers.CartHelper"/&gt; class.
        /// &lt;/summary&gt;
        /// &lt;param name="cartName"&gt;
        /// The cart Name.
        /// &lt;/param&gt;
        /// &lt;returns&gt;
        /// The &lt;see cref="ICartHelperWrapper"/&gt;.
        /// &lt;/returns&gt;
        public ICartHelperWrapper Create(string cartName)
        {
            cartHelper = new CartHelper(cartName);
            return this;
        }

        /// &lt;summary&gt;
        /// Accepts the changes.
        /// &lt;/summary&gt;
        /// &lt;exception cref="T:Mediachase.Commerce.Orders.Exceptions.OrderException"&gt;If a cart with same CustomerId, Name, and MarketId already exist.&lt;/exception&gt;
        public void AcceptChanges()
        {
            cartHelper.Cart.AcceptChanges();
        }

        /// &lt;summary&gt;
        /// Adds the entry with default warehouse code
        /// &lt;/summary&gt;
        /// &lt;param name="fullEntry"&gt;
        /// The full Entry.
        /// &lt;/param&gt;
        /// &lt;param name="quantity"&gt;
        /// The quantity.
        /// &lt;/param&gt;
        /// &lt;param name="fixedQuantity"&gt;
        /// If true, lineitem's qty will be set to &lt;paramref name="quantity"/&gt; value. Otherwise, &lt;paramref name="quantity"/&gt; will be added to the current line item's qty value.
        /// &lt;/param&gt;
        /// &lt;returns&gt;
        /// The line item.
        /// &lt;/returns&gt;
        public LineItem AddEntry(Entry fullEntry, int quantity, bool fixedQuantity)
        {
            return cartHelper.AddEntry(fullEntry, quantity, fixedQuantity);
        }

        /// &lt;summary&gt;
        /// Runs the workflow and generates the error message for all the warnings.
        /// &lt;/summary&gt;
        /// &lt;param name="name"&gt;The name.&lt;/param&gt;
        public void RunWorkFlow(string name)
        {
            cartHelper.RunWorkflow(name);
        }

        /// &lt;summary&gt;
        /// Deletes the current basket instance from the database.
        /// &lt;/summary&gt;
        public void Delete()
        {
            cartHelper.Delete();
        }
    }
}</code></pre>
<pre class="Plum_Code_Box">

You can see that we have to also wrap the line items in its own wrapper, so lets do that.

<pre class="Plum_Code_Box"><code class="csharp">// Copyright 2015 Green Man Gaming

using System;
using System.Runtime.Serialization;

using Mediachase.Commerce.Orders;

namespace GMGShop.Domain.Wrappers
{
    public class LineItemWrapper : ILineItemWrapper
    {
        private readonly LineItem lineItem;

        /// &lt;summary&gt;
        /// Initializes a new instance of the &lt;see cref="LineItemWrapper"/&gt; class.
        /// &lt;/summary&gt;
        /// &lt;param name="lineItem"&gt;&lt;/param&gt;
        public LineItemWrapper(LineItem lineItem)
        {
            this.lineItem = lineItem;
        }

        /// &lt;summary&gt;
        /// Initializes a new instance of the &lt;see cref="LineItemWrapper"/&gt; class.
        /// &lt;/summary&gt;
        public LineItemWrapper()
        {
            lineItem = new LineItem();
        }

        /// &lt;summary&gt;
        /// Gets the list of discounts that were applied to that particular line item.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The discounts.
        /// &lt;/value&gt;
        public LineItemDiscountCollection Discounts
        {
            get
            {
                return lineItem.Discounts;
            }
        }

        /// &lt;summary&gt;
        /// Gets the parent Order Form.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The parent.
        /// &lt;/value&gt;
        public OrderForm Parent
        {
            get
            {
                return lineItem.Parent;
            }
        }

        /// &lt;summary&gt;
        /// Gets the line item id.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The line item id.
        /// &lt;/value&gt;
        public int LineItemId
        {
            get
            {
                return lineItem.LineItemId;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the order form id.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The order form id.
        /// &lt;/value&gt;
        public int OrderFormId
        {
            get
            {
                return lineItem.OrderFormId;
            }

            set
            {
                lineItem.OrderFormId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the order group id.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The order group id.
        /// &lt;/value&gt;
        public int OrderGroupId
        {
            get
            {
                return lineItem.OrderGroupId;
            }

            set
            {
                lineItem.OrderGroupId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the catalog.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The catalog.
        /// &lt;/value&gt;
        public string Catalog
        {
            get
            {
                return lineItem.Catalog;
            }

            set
            {
                lineItem.Catalog = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the catalog node.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The catalog node.
        /// &lt;/value&gt;
        public string CatalogNode
        {
            get
            {
                return lineItem.CatalogNode;
            }

            set
            {
                lineItem.CatalogNode = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the parent catalog entry id. Typically for Product/Sku(Variation) types of products, this will be a product code.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The parent catalog entry id.
        /// &lt;/value&gt;
        public string ParentCatalogEntryId
        {
            get
            {
                return lineItem.ParentCatalogEntryId;
            }

            set
            {
                lineItem.ParentCatalogEntryId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the catalog entry code.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The catalog entry id.
        /// &lt;/value&gt;
        public string CatalogEntryId
        {
            get
            {
                return lineItem.CatalogEntryId;
            }

            set
            {
                lineItem.CatalogEntryId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the quantity.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The quantity.
        /// &lt;/value&gt;
        public Decimal Quantity
        {
            get
            {
                return lineItem.Quantity;
            }

            set
            {
                lineItem.Quantity = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the min quantity.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The min quantity.
        /// &lt;/value&gt;
        public Decimal MinQuantity
        {
          get
          {
              return lineItem.MinQuantity;
          }

          set
          {
              lineItem.MinQuantity = value;
          }
        }

        /// &lt;summary&gt;
        /// Gets or sets the max quantity.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The max quantity.
        /// &lt;/value&gt;
        public Decimal MaxQuantity
        {
            get
            {
                return lineItem.MaxQuantity;
            }

            set
            {
                lineItem.MaxQuantity = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the placed price.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The placed price.
        /// &lt;/value&gt;
        public Decimal PlacedPrice
        {
            get
            {
                return lineItem.PlacedPrice;
            }

            set
            {
                lineItem.PlacedPrice = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the list price. The price that the item is listed in the catalog.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The list price.
        /// &lt;/value&gt;
        public Decimal ListPrice
        {
            get
            {
                return lineItem.ListPrice;
            }

            set
            {
                lineItem.ListPrice = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the line item discount amount.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The line item discount amount.
        /// &lt;/value&gt;
        public Decimal LineItemDiscountAmount
        {
            get
            {
                return lineItem.LineItemDiscountAmount;
            }

            set
            {
                lineItem.LineItemDiscountAmount = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the order level discount amount.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The order level discount amount.
        /// &lt;/value&gt;
        public Decimal OrderLevelDiscountAmount
        {
            get
            {
                return lineItem.OrderLevelDiscountAmount;
            }

            set
            {
                lineItem.OrderLevelDiscountAmount = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the shipping address name.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The shipping address id.
        /// &lt;/value&gt;
        public string ShippingAddressId
        {
            get
            {
                return lineItem.ShippingAddressId;
            }

            set
            {
                lineItem.ShippingAddressId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the name of the shipping method.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The name of the shipping method.
        /// &lt;/value&gt;
        public string ShippingMethodName
        {
            get
            {
                return lineItem.ShippingMethodName;
            }

            set
            {
                lineItem.ShippingMethodName = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the shipping method id.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The shipping method id.
        /// &lt;/value&gt;
        public Guid ShippingMethodId
        {
            get
            {
                return lineItem.ShippingMethodId;
            }

            set
            {
                lineItem.ShippingMethodId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the extended price.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The extended price.
        /// &lt;/value&gt;
        public Decimal ExtendedPrice
        {
            get
            {
                return lineItem.ExtendedPrice;
            }

            set
            {
                lineItem.ExtendedPrice = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the description.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The description.
        /// &lt;/value&gt;
        public string Description
        {
            get
            {
                return lineItem.Description;
            }

            set
            {
                lineItem.Description = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the status.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The status.
        /// &lt;/value&gt;
        public string Status
        {
            get
            {
                return lineItem.Status;
            }

            set
            {
                lineItem.Status = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the name of the display.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The name of the display.
        /// &lt;/value&gt;
        public string DisplayName
        {
            get
            {
                return lineItem.DisplayName;
            }

            set
            {
                lineItem.DisplayName = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets a value indicating whether [allow backorders and preorders]
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// &lt;c&gt;true&lt;/c&gt; if [allow backorders and preorders]; otherwise, &lt;c&gt;false&lt;/c&gt;.
        /// &lt;/value&gt;
        public bool AllowBackordersAndPreorders
        {
            get
            {
                return lineItem.AllowBackordersAndPreorders;
            }

            set
            {
                lineItem.AllowBackordersAndPreorders = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the in stock quantity.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The in stock quantity.
        /// &lt;/value&gt;
        public Decimal InStockQuantity
        {
            get
            {
                return lineItem.InStockQuantity;
            }

            set
            {
                lineItem.InStockQuantity = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the preorder quantity.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The preorder quantity.
        /// &lt;/value&gt;
        public Decimal PreorderQuantity
        {
            get
            {
                return lineItem.PreorderQuantity;
            }

            set
            {
                lineItem.PreorderQuantity = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the backorder quantity.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The backorder quantity.
        /// &lt;/value&gt;
        public Decimal BackorderQuantity
        {
            get
            {
                return lineItem.BackorderQuantity;
            }

            set
            {
                lineItem.BackorderQuantity = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the inventory status.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The inventory status.
        /// &lt;/value&gt;
        public int InventoryStatus
        {
            get
            {
                return lineItem.InventoryStatus;
            }

            set
            {
                lineItem.InventoryStatus = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the line item ordering.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The line item ordering.
        /// &lt;/value&gt;
        public DateTime LineItemOrdering
        {
          get
          {
              return lineItem.LineItemOrdering;
          }

          set
          {
              lineItem.LineItemOrdering = value;
          }
        }

        /// &lt;summary&gt;
        /// Gets or sets the configuration id. The external component configuration id, used by bundle, kits and other
        ///            combination products.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The configuration id.
        /// &lt;/value&gt;
        public string ConfigurationId
        {
            get
            {
                return lineItem.ConfigurationId;
            }

            set
            {
                lineItem.ConfigurationId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the provider id. Used to identify the line item in the extrnal system.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The provider id.
        /// &lt;/value&gt;
        public string ProviderId
        {
            get
            {
                return lineItem.ProviderId;
            }

            set
            {
                lineItem.ProviderId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the RMA item reason. ("Corrupted", "Mismatch" etc.)
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The rma reason.
        /// &lt;/value&gt;
        public string ReturnReason
        {
            get
            {
                return lineItem.ReturnReason;
            }

            set
            {
                lineItem.ReturnReason = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the returned in stock quantity
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The return quantity.
        /// &lt;/value&gt;
        public Decimal ReturnQuantity
        {
            get
            {
                return lineItem.ReturnQuantity;
            }

            set
            {
                lineItem.ReturnQuantity = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the identity orig line item for RMA line item
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The rma orig line item id.
        /// &lt;/value&gt;
        public int? OrigLineItemId
        {
            get
            {
                return lineItem.OrigLineItemId;
            }

            set
            {
                lineItem.OrigLineItemId = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the warehouse code
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The warehouse code.
        /// &lt;/value&gt;
        public string WarehouseCode
        {
            get
            {
                return lineItem.WarehouseCode;
            }

            set
            {
                lineItem.WarehouseCode = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets a value indicating whether if
        /// the inventory for this item has already been allocated from
        /// the inventory system
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The is inventory allocated.
        /// &lt;/value&gt;
        public bool IsInventoryAllocated
        {
            get
            {
                return lineItem.IsInventoryAllocated;
            }

            set
            {
                lineItem.IsInventoryAllocated = value;
            }
        }

        /// &lt;summary&gt;
        /// Gets or sets the old quantity.
        /// &lt;/summary&gt;
        /// &lt;value&gt;
        /// The old quantity.
        /// &lt;/value&gt;
        public Decimal OldQuantity
        {
            get
            {
                return lineItem.OldQuantity;
            }

            set
            {
                lineItem.OldQuantity = value;
            }
        }

        /// &lt;summary&gt;
        /// Sets the parent.
        /// &lt;/summary&gt;
        /// &lt;param name="parent"&gt;
        /// The parent.
        /// &lt;/param&gt;
        public void SetParent(object parent)
        {
            lineItem.SetParent(parent);
        }

        /// &lt;summary&gt;
        /// Accepts the changes.
        /// &lt;/summary&gt;
        public void AcceptChanges()
        {
            lineItem.AcceptChanges();
        }

        /// &lt;summary&gt;
        /// Gets the object data.
        /// &lt;/summary&gt;
        /// &lt;param name="info"&gt;
        /// The info.
        /// &lt;/param&gt;
        /// &lt;param name="context"&gt;
        /// The context.
        /// &lt;/param&gt;
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            lineItem.GetObjectData(info, context);
        }

        /// &lt;summary&gt;
        /// Deletes the object data
        /// &lt;/summary&gt;
        public void Delete()
        {
            lineItem.Delete();
        }
      }
    }</code></pre>
<pre class="Plum_Code_Box">

So now we can finally write our test.

<pre class="Plum_Code_Box"><code class="csharp">// Copyright 2015 Green Man Gaming

CartWrapper = new Mock&lt;ICartHelperWrapper&gt;();

factoryMock.Setup(x =&gt; x.GetCartHelper()).Returns(CartWrapper.Object);

var firstLineItem = new Mock&lt;ILineItemWrapper&gt;();

firstLineItem
    .SetupGet(x =&gt; x.DisplayName)
    .Returns("My Test Product");

firstLineItem
    .SetupGet(x =&gt; x.CatalogEntryId)
   .Returns("Borderlands - PC");

firstLineItem
    .SetupGet(x =&gt; x.Quantity)
    .Returns(BorderlandsQnty);

firstLineItem
    .SetupSet(x =&gt; x.Quantity = It.IsAny&lt;Decimal&gt;())
    .Callback&lt;Decimal&gt;(value =&gt; BorderlandsQnty = value);

LineItems = new List&lt;ILineItemWrapper&gt; { firstLineItem.Object };

CartWrapper.SetupGet(x =&gt; x.LineItems).Returns(LineItems);
CartWrapper.Setup(x =&gt; x.AcceptChanges());

new BasketHelper(factoryMock.Object);
//The test
BasketHelper.UpdateLineItemQuantity("Borderlands - PC", 3);

CartWrapper
    .Verify(x =&gt; x.AcceptChanges(), Times.Once);

Assert.AreEqual(3, BorderlandsQnty);</code></pre>
<pre class="Plum_Code_Box">

In essence to test 7 lines of code we have to write over 700 lines of wrapper code. If the team had spent 30 minutes to write interfaces around the two classes, it would have saved a lot of time wrapping the classes. This is just one example of some of the extra work we have to go to test commerce code.

If you fancy helping us with our project, we are currently hiring a full time EpiServer Engineer. Send your CV to [email protected]

 

 

Saturday, February 28th, 2015 Episerver

Leave a Reply

 
October 2024
M T W T F S S
« Jan    
 123456
78910111213
14151617181920
21222324252627
28293031