Using MOQ in unit-testing for a shipping calculator

Posted on

Problem

I am fairly new to writing unit tests, and I was wondering if this approach I have taken is on the right track?

Given the following interfaces/classes:

public interface ILoggingService
{
    void Log(string message);
}

public interface IShoppingCart
{
    IList<IShoppingCartItem> Items { get; }
}

public interface IShoppingCartItem
{
    string Sku { get; set; }
    string Name { get; set; }
    string Description { get; set; }
    decimal Price { get; set; }
    int Quantity { get; set; }
}

public interface IShippingCalculator
{
    decimal Calculate();
}

public class ShoppingCartItem : IShoppingCartItem
{
    public string Sku { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

public class ShippingCalculator : IShippingCalculator
{
    private readonly ILoggingService loggingService;
    private readonly IShoppingCart shoppingCart;

    public ShippingCalculator(IShoppingCart shoppingCart, ILoggingService loggingService)
    {
        this.shoppingCart = shoppingCart;
        this.loggingService = loggingService;
    }

    public decimal Calculate()
    {
        if (shoppingCart.Items.Count == 0)
            return 0;

        var pricePerItem = 3;

        return shoppingCart.Items.Sum(x => x.Quantity * pricePerItem);
    }
}

Given the requirements of:

The Shipping Calculator must calculate shipping at the rate of $3.00 per item.

Does this unit test look correct?

public class ShippingCalculatorTests
    {
        [Fact]
        public void Test1()
        {
            //Arrange
            var cart = new Mock<IShoppingCart>();
            cart.Setup(m => m.Items).Returns(
                new List<IShoppingCartItem>() { 
                    new ShoppingCartItem() { Quantity = 5 },
                    new ShoppingCartItem() { Quantity = 3 }
                }
            );

            var logger = new Mock<ILoggingService>();

            var calc = new ShippingCalculator(cart.Object, logger.Object);

            //Act
            var result = calc.Calculate();

            //Assert
            Assert.Equal(24, result);
        }
    }

Solution

Your test is testing one thing only which is a very good thing. It is better to have many simple tests instead of one massive and it makes bug finding much easier.

First of all, I would suggest using more descriptive names for both methods and test. Consider calling Calculate() method differently, i.e. CalculateTotalShippingPrice, so that it indicates what it is doing.

Use test names which specify what is being tested, what is the state under test and expected behaviour as in: https://stackoverflow.com/questions/155436/unit-test-naming-best-practices.
Test1 could be called Calculate_WhenShoppingCartNotEmpty_CorrectlyCalculatesTotalShippingPrice

Make sure you take into account different scenarios, it is worth adding another test: Calculate_WhenShoppingCartEmpty_ReturnsZero.

Unit Testing means testing single functionality at a time which is there above.
More test methods can be added similarly for example when cart is empty, when exception, so on.
Moving further you can check whether some property has been set to specific using VerifySet or a function is called or not.
The approach is correct, as already said Method names need to be descriptive, you can follow any consistent pattern for that like: MethodName_TestSum_When_SomethingIsNUll().

Leave a Reply

Your email address will not be published. Required fields are marked *