Search code examples
unit-testingtddmoqsolid-principlesseparation-of-concerns

How to unit test a method that is having multiple object creation in switch statement? How to Mock them?


Another question is if there is any better way to write this method?

Public decimal CalculateTotalPrice(List<product> items)
{
    decimal totalPrice = 0.m;

    foreach(Product p in items)
    {
        if(p.Offer == "")
            calc = new DefaultCalc();
        else if(p.Offer == "BuyOneGetOneFree")
            calc = new BuyOneGetOneFreeCalc();
        else if(p.Offer == "ThreeInPriceOfTwo")
            calc = new ThreeInPriceOfTwoCalc()

        totalPrice += calc.Calculate(p.Quantity, p.UnitPrice);
    }
    return totalPrice;
}

Solution

  • You should probably review Polly Want a Message, by Sandi Metz

    How to unit test a method that is having multiple object creation in switch statement?

    An important thing to notice here is that the switch statement is an implementation detail. From the point of view of the caller, this thing is just a function

    Public decimal CalculateTotalPrice(List<product> items);
    

    If the pricing computations are fixed, you can just use the usual example based tests:

    assertEquals(expectedPrice, CalculateTotalPrice(items));
    

    But if they aren't fixed, you can still do testing based on the properties of the method. Scott Wlaschin has a really good introduction to property based testing. Based on the logic you show here, there are some things we can promise about prices, without knowing anything about the strategies in use

    • the price should always be greater than zero.
    • the price of a list of items is the same as the sum of the prices of the individual items.

    if there is any better way to write this method?

    You might separate choosing the pricing strategy from using the strategy. As Sandi notes, that sort of construct often shows up in more than once place.

    foreach(Product p in items)
    {
        calc = pricing(p.Offer);
        totalPrice += calc.Calculate(p.Quantity, p.UnitPrice);
    }
    

    "pricing" would then become something that you pass into this function (either as an argument, or as a dependency).

    In effect, you would end up with three different kinds of test.

    • Checks that pricing returns the right pricing strategy for each offer.
    • Checks that each strategy performs its own calculation correctly.
    • Checks that CalculateTotalPrice compute the sum correctly.

    Personally, I prefer to treat the test subject as a single large black box, but there are good counter arguments. Horses for courses.