Friday, February 05, 2010

Day 2 of 5 Day TDD Kata: "City Tax Validation is Teased Out into new ITaxesService"

I've just completed Day 2 of the 5 Day TDD Kata.

The project is growing into multiple tests and classes, so I will only post a brief code sample below; full samples are posted to github here.

Here's the kata:

DAY 2: TEASING OUT DOMAIN OBJECT: ITaxesService
  1. Create JurisdictionEnum { City, State, Country }
  2. Alter TaxesTest test methods to now test for Jurisdiction as required 4th Tax property. 
  3. Add new constructor parameter to City: ITaxesService, so that City constructor calls in CityTests will no longer compile.
  4. Comment out ALL public and private methods in City, so that the Add(Tax tax) method call in CityTests will no longer compile.
  5. Since the tests no longer compile, comment out all test methods in the CityTests class.
  6. Reference a mocking framework and add a using statement to CityTests class.
  7. Create new test in CityTests which verifies that when City is asked to AddTax(), that it delegates tax adding to the mocked ITaxesService. City should be agnostic to tax storage.
  8. Create new test in ProvinceTests which verifies that when Province is asked to AddTax(), that it delegates tax adding to the mocked ITaxesService. Province should be agnostic to tax storage.
  9. Create new test in CountryTests which verifies that when Country is asked to AddTax(), that it delegates tax adding to the mocked ITaxesService. Country should be agnostic to tax storage.
  10. Create a TaxesServiceTests class.
  11. Move the commented-out CityTest methods to TaxesServiceTests class and repurpose them to address the TaxesService.Add() method and Taxes property. 
  12. Notes: When creating TaxesService, make it implement ITaxesService, and use a parameterless constructor. Remember to move the commented out public and private methods from City class to the new TaxesService class--you'll need them for the tests to pass.
  13. Validate that tax duplication checking logic now constrains on BOTH TaxType AND Jurisdiction.
  14. For extra points: Create a test which instantiates City, Province, and Country, injecting each with a common stub of ITaxesService and adding a tax from each jurisdiction. Validate that 3 taxes have been added to ITaxesService stub. Also validate that each class' Taxes collection returns only taxes for that jurisdiction.


CityTests.cs

[TestFixture]
public class CityTests
{
    private MockRepository _mockRepository;
    private ITaxesService _mockTaxesService;

    [SetUp]
    public void SetUp()
    {
        _mockRepository = new MockRepository();
        _mockTaxesService = _mockRepository.StrictMock();
    }

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

    [Test]
    public void CityDelegatesAddedTaxesToInjectedTaxesService()
    {
        // expectations
        var pstTax = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6), JurisdictionEnum.City);
        _mockTaxesService.AddTax(pstTax);

        _mockRepository.ReplayAll();

        var city = new City("Winnipeg", _mockTaxesService);
        city.AddTax(pstTax);            
    }
}


TaxesServiceTests.cs

[TestFixture]
public class TaxesServiceTests
{
    [Test]
    public void TaxesServiceCanAccumulateTaxes()
    {
        var taxesService = new TaxesService();
        var pstTax = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6), JurisdictionEnum.City);
        taxesService.AddTax(pstTax);

        Assert.IsTrue(taxesService.Taxes.Contains(pstTax));
    }

    [Test]
    [ExpectedException(typeof(DuplicateTaxesException))]
    public void TaxesServiceRejectsDuplicateTaxes()
    {
        var taxesService = new TaxesService();
        var pstTax1 = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6), JurisdictionEnum.City);
        var pstTax2 = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6), JurisdictionEnum.City);
        taxesService.AddTax(pstTax1);
        taxesService.AddTax(pstTax2);
    }

    [Test]
    [ExpectedException(typeof(OverlappingTaxTypesException))]
    public void TaxesServiceRejectsOverlappingTaxesPerTaxType()
    {
        var taxesService = new TaxesService();
        var pstTax1 = new Tax("PST", DateTime.Today, DateTime.Today.AddMonths(6), JurisdictionEnum.City);
        var pstTax2 = new Tax("PST", DateTime.Today.AddMonths(6), DateTime.Today.AddYears(1), JurisdictionEnum.City);
        taxesService.AddTax(pstTax1);
        taxesService.AddTax(pstTax2);
    }
}

1 comment:

Polemann said...

Why do the tests not test valid test e.g.

Can Add Tax Before Period
Can Add Tax After Existing Periods
Can't Add Overlapping End only start is tested
Can Add TaxGaps