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.