Monday, February 08, 2010

Day 5 of 5 Day TDD Kata: "Invoice Presenter Adds and Displays Invoice with Taxes and Totals"

And that's it: the 5 Day TDD Kata is now complete!

As before, I will only post a brief code sample below; full samples are posted to github here.

Here's the kata:

DAY 5: InvoicePresenter ADDS AND DISPLAYS INVOICE WITH TAXES AND TOTALS
      The culmination. This kata works with everything you have built leading up to it.
  1. Create a namespace for Repository.
  2. Create CustomerTests. Verify when Customer adds an Invoice, that Customer.Invoices increments.
  3. Create InvoicePresenterTests with 4 mocked interfaces: ITaxesRepository, ICustomerRepository, IInvoiceRepository, and IInvoiceView.
  4. Create the following IInvoiceView events, each with a default EventHandler:
    • GetCustomer event
    • AddInvoiceLine event
    • CalculateTotals event
    • SaveInvoice event 
  5. For each one, verify that InvoicePresenter's constructor attaches that event to an event handler. (You can expect each event to be assigned to null, and instruct the mock to ignore the argument. For subsequent tests, you only need to get the event if the current test needs to verify behaviour that occurs when the event is raised.)
  6. When GetCustomer event is raised, verify that
    a) IInvoiceView.CustomerCode is assigned to ICustomerRepository.FindCustomerByCode().
    b) the returned Customer is retrieved, and FirstName and LastName are assigned to IInvoiceView.FirstName and IInvoiceView.LastName. 
  7. When AddInvoiceLine event is raised, verify that:
    a) IInvoiceView.Quantity and IInvoiceView.Amount are retrieved and assigned to InvoiceItem constructor.
    b) ITaxesRepository.GetTaxesService() is called and returns ITaxesService.
    c) Invoice is instantiated with ITaxesService, and InvoiceItem is added to Invoice.AddLineItem()
    d) Invoice.LineItems is assigned to IInvoiceView.InvoiceLineItems property.
    e) When the last expectation fails, add equality checking (IsEqual() and GetHashCode()) to InvoiceItem class, based on properties of InvoiceItem (quantity and amount.)
  8. When CalculateTotals event is raised, verify that:
    a) Invoice.SubTotal is assigned to IInvoiceView.Subtotal.
    b) Invoice.TaxCalculations are assigned to IInvoiceView.TaxCalculations.
    c) Invoice.Total is assigned to IInvoiceView.Total. 
  9. When SaveInvoice event is raised, verify that:
    a) IInvoiceRepository.SaveInvoice() is passed the current Invoice instance.
    b) When this expecation fails, add equality checking (IsEqual() and GetHashCode() to Invoice, based on 0 items equal, or equality of items.

InvoicePresenterTests.cs

[TestFixture]
public class InvoicePresenterTests
{
    private MockRepository _mockRepository;
    private ICustomerRepository _mockCustomerRepository;
    private ITaxesRepository _mockTaxesRepository;
    private IInvoiceRepository _mockInvoiceRepository;
    private IInvoiceView _mockInvoiceView;

    [SetUp]
    public void SetUp()
    {
        _mockRepository = new MockRepository();
        _mockCustomerRepository = _mockRepository.StrictMock();
        _mockTaxesRepository = _mockRepository.StrictMock();
        _mockInvoiceRepository = _mockRepository.StrictMock();
        _mockInvoiceView = _mockRepository.StrictMock();
    }

    [TearDown]
    public void TearDown()
    {
        _mockRepository.VerifyAll();
    }

    [Test]
    public void InvoicePresenterAttachesAllViewEvents()
    {
        _mockInvoiceView.GetCustomer += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.AddInvoiceLine += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.CalculateTotals += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.SaveInvoice += null;
        LastCall.IgnoreArguments();

        _mockRepository.ReplayAll();

        var invoicePresenter = new InvoicePresenter(_mockCustomerRepository, _mockTaxesRepository, _mockInvoiceRepository, _mockInvoiceView);
    }

    [Test]
    public void InvoiceGetCustomerEventRetrievesCustomerInformation()
    {
        _mockInvoiceView.GetCustomer += null;
        var getCustomerEventRaiser = LastCall.IgnoreArguments().GetEventRaiser();
        _mockInvoiceView.AddInvoiceLine += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.CalculateTotals += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.SaveInvoice += null;
        LastCall.IgnoreArguments();

        const string customerCode = "JIMSMI";
        var customer = new Customer();
        Expect.Call(_mockInvoiceView.CustomerCode).Return(customerCode);
        Expect.Call(_mockCustomerRepository.FindCustomerByCode(customerCode)).Return(customer);
        _mockInvoiceView.CustomerFirstName = customer.FirstName;
        _mockInvoiceView.CustomerLastName = customer.LastName;

        _mockRepository.ReplayAll();

        var invoicePresenter = new InvoicePresenter(_mockCustomerRepository, _mockTaxesRepository, _mockInvoiceRepository, _mockInvoiceView);
        getCustomerEventRaiser.Raise(_mockInvoiceView, EventArgs.Empty);
    }

    [Test]
    public void InvoiceAddLineItemEventAddsLineItem()
    {
        _mockInvoiceView.GetCustomer += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.AddInvoiceLine += null;
        var addInvoiceLineEventRaiser = LastCall.IgnoreArguments().GetEventRaiser();
        _mockInvoiceView.CalculateTotals += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.SaveInvoice += null;
        LastCall.IgnoreArguments();

        const int quantity = 3;
        const decimal amount = 35.00M;
        ITaxesService taxesService = new TaxesService();
        Expect.Call(_mockInvoiceView.Quantity).Return(quantity);
        Expect.Call(_mockInvoiceView.Amount).Return(amount);
        Expect.Call(_mockTaxesRepository.GetTaxesService()).Return(taxesService);
        var invoiceItem = new InvoiceItem(quantity, amount);
        var invoice = new Invoice(taxesService);
        invoice.AddLineItem(invoiceItem);
        _mockInvoiceView.InvoiceLineItems = invoice.InvoiceItems;

        _mockRepository.ReplayAll();

        var invoicePresenter = new InvoicePresenter(_mockCustomerRepository, _mockTaxesRepository, _mockInvoiceRepository, _mockInvoiceView);
        addInvoiceLineEventRaiser.Raise(_mockInvoiceView, EventArgs.Empty);
    }

    [Test]
    public void InvoiceCalculateTotalsEventDisplaysSubTotalTaxesAndTotal()
    {
        _mockInvoiceView.GetCustomer += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.AddInvoiceLine += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.CalculateTotals += null;
        var calculateTotalsEventRaiser = LastCall.IgnoreArguments().GetEventRaiser();
        _mockInvoiceView.SaveInvoice += null;
        LastCall.IgnoreArguments();

        ITaxesService taxesService = new TaxesService();
        Expect.Call(_mockTaxesRepository.GetTaxesService()).Return(taxesService);
        var invoice = new Invoice(taxesService);
        _mockInvoiceView.SubTotal = invoice.SubTotal;
        _mockInvoiceView.TaxCalculations = invoice.TaxCalculations;
        _mockInvoiceView.Total = invoice.Total;

        _mockRepository.ReplayAll();

        var invoicePresenter = new InvoicePresenter(_mockCustomerRepository, _mockTaxesRepository, _mockInvoiceRepository, _mockInvoiceView);
        calculateTotalsEventRaiser.Raise(_mockInvoiceView, EventArgs.Empty);
    }

    [Test]
    public void InvoiceSaveInvoiceToRepository()
    {
        _mockInvoiceView.GetCustomer += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.AddInvoiceLine += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.CalculateTotals += null;
        LastCall.IgnoreArguments();
        _mockInvoiceView.SaveInvoice += null;
        var saveInvoiceEventRaiser = LastCall.IgnoreArguments().GetEventRaiser();

        ITaxesService taxesService = new TaxesService();
        Expect.Call(_mockTaxesRepository.GetTaxesService()).Return(taxesService);
        var invoice = new Invoice(taxesService);
        _mockInvoiceRepository.SaveInvoice(invoice);

        _mockRepository.ReplayAll();

        var invoicePresenter = new InvoicePresenter(_mockCustomerRepository, _mockTaxesRepository, _mockInvoiceRepository, _mockInvoiceView);
        saveInvoiceEventRaiser.Raise(_mockInvoiceView, EventArgs.Empty);
    }
}


InvoicePresenter.cs

public class InvoicePresenter
{
    private readonly ICustomerRepository _customerRepository;
    private readonly ITaxesRepository _taxesRepository;
    private readonly IInvoiceRepository _invoiceRepository;
    private readonly IInvoiceView _invoiceView;
    private Invoice _invoice;

    public InvoicePresenter(ICustomerRepository customerRepository, ITaxesRepository taxesRepository, 
                                IInvoiceRepository invoiceRepository, IInvoiceView invoiceView)
    {
        _customerRepository = customerRepository;
        _taxesRepository = taxesRepository;
        _invoiceRepository = invoiceRepository;
        _invoiceView = invoiceView;

        _invoiceView.GetCustomer += new System.EventHandler(InvoiceViewGetCustomer);
        _invoiceView.AddInvoiceLine += new System.EventHandler(InvoiceViewAddInvoiceLine);
        _invoiceView.CalculateTotals += new System.EventHandler(InvoiceViewCalculateTotals);
        _invoiceView.SaveInvoice += new System.EventHandler(InvoiceViewSaveInvoice);
    }

    void InvoiceViewSaveInvoice(object sender, System.EventArgs e)
    {
        var invoice = GetInvoice();
        _invoiceRepository.SaveInvoice(invoice);
    }

    void InvoiceViewCalculateTotals(object sender, System.EventArgs e)
    {
        var invoice = GetInvoice();
        _invoiceView.SubTotal = invoice.SubTotal;
        _invoiceView.TaxCalculations = invoice.TaxCalculations;
        _invoiceView.Total = invoice.Total;
    }

    void InvoiceViewAddInvoiceLine(object sender, System.EventArgs e)
    {
        var invoice = GetInvoice();
        invoice.AddLineItem(new InvoiceItem(_invoiceView.Quantity, _invoiceView.Amount));
        _invoiceView.InvoiceLineItems = invoice.InvoiceItems;
    }

    private Invoice GetInvoice()
    {
        if (_invoice == null)
        {
            _invoice = new Invoice(_taxesRepository.GetTaxesService());
        }
        return _invoice;
    }

    void InvoiceViewGetCustomer(object sender, System.EventArgs e)
    {
        var customer = _customerRepository.FindCustomerByCode(_invoiceView.CustomerCode);
        _invoiceView.CustomerFirstName = customer.FirstName;
        _invoiceView.CustomerLastName = customer.LastName;
    }
}

No comments: