> For the complete documentation index, see [llms.txt](https://raviram.gitbook.io/tdd/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://raviram.gitbook.io/tdd/tdd-exercises-2/shopping-cart/test-buy-2-get-1.md).

# 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`**

```csharp
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.

<details>

<summary>Installing Moq</summary>

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

```bash
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.

</details>

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.

```csharp
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**

```csharp
[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**:

  ```csharp
  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.

```csharp
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`.
