Sunday, February 12, 2012

DDD Kata, Part 4 (Service Layer with Mocks)

Pre-Requisite: DDD Kata Part 3

Kata Review
In part 2 of the kata, you built a simple service test to demonstrate the passing of the Item from the Inventory aggregate root to the Invoice aggregate root. In part 3 of the kata, you created IUnitOfWork interface to manage atomic transactions with commit and rollbacks.

Now we need to design the real service.

In this test we will "inject" repository interfaces into the service class constructor to do the work of persisting the state changes to our domain entities. The UnitOfWork we created in part 3 of the kata will assist us in this effort.

Completed kata example on github: DDD Kata Part 4 sample code (github)

NOTE   If you haven't already download RhinoMocks, download it and add the DLLs to a 3rd Party Libs directory for reference.

1. Open the previous solution you created in kata 3.
2. Add a reference to RhinoMocks.DLL to the library "Kata.Services.Tests.Unit".
3. Use RhinoMocks to mock the following interfaces (use Resharper to generate the new ones).
NOTE Your mocking levels are stub, dynamic, and strict. The 3rd choice strictly enforces the test specfiications. Start with a strict implementation for now.
  • IInvoiceRepository
  • IInventoryRepository
  • IUnitOfWorkFactory
  • IUnitOfWork

private MockRepository _mockRepository;
private IInvoiceRepository _invoiceRepository;
private IInventoryRepository _inventoryRepository;
private IUnitOfWorkFactory _unitOfWorkFactory;
private IUnitOfWork _unitOfWork;

public void SetUp()
    _mockRepository = new MockRepository();
    _invoiceRepository = _mockRepository.StrictMock();
    _inventoryRepository = _mockRepository.StrictMock();
    _unitOfWorkFactory = _mockRepository.StrictMock();
    _unitOfWork = _mockRepository.StrictMock();

4. Create a test.
5. Enter code comments, and basic Rhino Mocks method calls, to generate a test skeleton:

public void CreateSimpleInvoiceMethod_ProductCodeAndSerialNumberInputs_GenratesSimpleInvoice()
    // declare constants

    // expectations
    // call to new service method

Now populate the test skeleton as follows:
6. In declare constants, create constants for productCode and serialNumber
7. In declare constants, build an Inventory instance using method StockItemBy()
8. In declare constants, create an Invoice
9. RhinoMocks tests based on object equality, so make sure you have assigned Ids to all your objects.

// declare constants
const string productCode = "ABCD1234";
const string serialNumber = "BB2135315";
var inventory = new Inventory { Id = 1234 };
inventory.StockItemBy(productCode, serialNumber);
var invoice = new Invoice() { Id = 1234 };

10. In expectations, expect that IUnitOfWorkFactory creates IUnitOfWork.
NOTE   If the Create() method exists on the implementation class (UnitOfWorkFactory) but not on the interface, use Resharper to generate it on the interface.
11. Expect that InventoryRepository.LoadInventoryByProduct(IUnitOfWork uow, string productCode) returns Inventory instance.
12. Add comment: // Call to Inventory.PullItemBy(productCode)
13. Add comment: // Call to Invoice.BillItem(item)
NOTE   These must be comments only as they aren't on mock objects. Actual calls would reduce inventory to zero in no context and cause a null error below.
14. Expect that InvoiceRepository.Save(IUnitOfWork uow, Invoice invoice) saves invoice.
15. Expect that IUnitOfWork.Commit() is called
NOTE   If the Commit() method exists on the implementation class (UnitOfWork) but not on the interface, use Resharper to generate it on the interface.
16. Expect that IUnitOfWork.Dispose() is called

// expectations
Expect.Call(_inventoryRepository.LoadInventoryByProduct(_unitOfWork, productCode)).Return(inventory);
// Call to Inventory.PullItemBy(productCode)
// Call to Invoice.BillItem(item)
_invoiceRepository.Save(_unitOfWork, invoice);

17. Between ReplayAll and VerifyAll, create InvoicingService instance with all mocked interfaces.


var sut = new InvoicingService(_unitOfWorkFactory, _inventoryRepository, _invoiceRepository);
Invoice actualInvoice = sut.CreateSimpleInvoice(productCode, serialNumber);


18. Now use Resharper to generate the method under test: InvoicingService.CreateSimpleInvoice(productCode,serialNumber).
19. Run the test and watch it fail.
20. Use the expectations set in the test to assemble the method.
21. Remember to wrap the call in IUnitOfWork using statement, and to commit at end of the block

public Invoice CreateSimpleInvoice(string productCode, string serialNumber)
    using(IUnitOfWork unitOfWork = _unitOfWorkFactory.Create())
        Inventory inventory = _inventoryRepository.LoadInventoryByProduct(unitOfWork, productCode);
        Item item = inventory.PullItemBy(productCode);
        var invoice = new Invoice();
        _invoiceRepository.Save(unitOfWork, invoice);
        return invoice;

22. Invoice will fail because its Id cannot be known, so MODIFY the expectation for InvoiceRepository.Save() by adding:
23. All tests should now pass.

This completes DDD kata part 3.

The next kata will introduce Fluent NHibernate as an implementation framework against the domain entities and repository interfaces that you have created so far.

No comments: