Test: Buy 2 Get 1

In the next requirement, we need to use an external service, IOfferService, to determine eligibility for the "Buy 2 Get 1 Free" offer for certain items only. This will affect how the totals are calculated.

Step 1: Create the interface IOfferService

public interface IOfferService
{
    bool IsEligibleForBuyTwoGetOne(string itemName);
}

Step 2: Write the Failing Test Case

Write a test case to verify that the offer is correctly applied using the IOfferService. Note that a mocking framework should be used to mock the IOfferService behavior.

Installing Moq

To install the Moq mocking library in your .NET project using the .NET Core Command-Line Interface (CLI), you can use the following command:

dotnet add package Moq

This command should be executed in the directory of your test project, where your .csproj file is located. Moq will then be added as a package reference to your project, and you can start using it to create mocks in your unit tests.

Test Case Setup:

Add a reference to a mocking library (like Moq) if not already added.

Modify the test constructor to now make use of new cart constructor which uses a service.

public class ShoppingCartTests
{
    private readonly ShoppingCart _cart;
    private readonly Mock<IOfferService> _mockOfferService;;

    //xUnit creates a new instance of the test class for each test, so it is safe to share context!
    public ShoppingCartTests()
    {
        _mockOfferService = new Mock<IOfferService>();
        _cart = new ShoppingCart(_mockOfferService.Object);
    }
    
    //other code...
}

Step 3: Write test case

[Fact]   
public void Apply_BuyTwoGetOneFreeOffer_WhenEligible()
{
    var itemName = "apple";
    var quantity = 3;
    var price = 1.00;
    var isEligible = true;

    var expectedTotal = 2.00;

    _mockOfferService.Setup(s => s.IsEligibleForBuyTwoGetOne(itemName)).Returns(isEligible);
    
    _cart.AddItem(new Item(itemName, quantity, price));

    var total = _cart.CalculateTotalCost();       

    total.Should().Be(expectedTotal);
}

Step 4: Modify the ShoppingCart (just to provision for enabling Injection of the service)

  • Guidance: Modify the ShoppingCart class to accept an IOfferService instance via constructor injection. Initially, do not change any logic in the methods.

  • Code:

    public class ShoppingCart
    {
        private readonly IOfferService _offerService;
    
        public ShoppingCart(IOfferService offerService)
        {
            _offerService = offerService;
        }
    
        // ... existing methods ...
    }

Step 5: Run the Test (Confirm Failure)

  • Action: Run the test to confirm it fails, as the logic to use the IOfferService is not implemented yet.

Step 6: Implement Logic to Pass the Test

  • Task: Implement the logic in the ShoppingCart class to use the IOfferService for determining offer eligibility.

  • Guidance: Ensure that the CalculateTotalCost method utilizes the IOfferService to apply discounts appropriately.

public class ShoppingCart
{
    private readonly IOfferService _offerService;

    public ShoppingCart(IOfferService offerService)
    {
        _offerService = offerService;
    }

    // ... existing methods ...

    public double CalculateTotalCost()
    {
        double total = 0.0;
        foreach (var item in Items)
        {
            bool isEligible = _offerService.IsEligibleForBuyTwoGetOne(item.Name);
            if (isEligible && item.Quantity >= 3)
            {
                int freeItems = item.Quantity / 3;
                total += (item.Quantity - freeItems) * item.Price;
            }
            else
            {
                total += item.Price * item.Quantity;
            }
        }
        return total;
    }
}

Step 7: Re-run the Test (Verify Success)

  • Action: Run the test again. The test should now pass, demonstrating that the shopping cart correctly interacts with the IOfferService.

Last updated