This is something hat has been rolling in my mind for quite some time now, but I still haven't found a good approach to it. So here's the thing. We have a server application that has quite a few complex use cases which involve a lot of ifs and elses and handling of customer data. I can't go into the exact details here so let's make up a sample for some context. Assume you have an order processing system where you process orders for a customer. We use Java/Spring with dependency injection and all the bells and whistles so our service would look like this:
class OrderService {
// these are all dependency-injected by Spring -leaving this out for clarity.
private CustomerService customerService;
private OrderDao orderDao;
private DiscountCodeService discountCodeService;
private CustomerLoyaltyProgramService loyaltyProgramService;
private TaxService taxService;
public Order createOrder(OrderForm form) {
// form is pre-validated through JSR303 annotations but there are
// some additional validations
// that need to be made
//
if (formHasError) {
throw SomeException();
}
// ok we can continue here.
Customer customer = customerService.getCustomerById(form.getCustomerId())
// build up an order from the form
Order order = new Order();
// copy over stuff..
// check if customer has a discount code
if (form.getDiscountCode() != null) {
discountCodeInfo = discountCodeService.getDiscountCodeInfoFor(form.getDiscountCode())
// now walk over line items to see if there is some applicable order line item.
}
// check if the customer is in any loyalty program that will give him
// additional discounts
List<LoyaltyProgram> programs = loyaltyProgramService.findByCustomer(customer);
// do some more ifs and elses to apply loyalty program discounts
// check applicable sales tax for customer
SalesTax tax = taxService.findSalesTaxFor(form.getAddress())
// do some tax calculations, more ifs and elses
// finally store order
orderDao.save(order);
return order;
}
}
Testing such a service with a unit test is basically a nightmare. You have to mock all the dependencies where the mocking depends on the flow through the method and the different business rules that are applied on certain cirumstances. At a certain amount of mocks and code paths your head will simply explode.
Another approach we tried was to split this up into several private (well protected, as you need to access them in a test) methods, which would make it more testable. However then our business logic is spread all over the place which makes it hard to follow in code and you still have to have some "master method" which calls all the little helper methods in correct order and you need to test that as well so you're basically back to square one.
So what I'm looking for is some way to reorganise this which allows you to test the methods easier without having to think about all the dependencies while still keeping the code maintainable and not spreading it all out to a thousand different places where you cannot follow the logic anymore. I figure that this is probably going to be some trade-off, but I wonder how you may have tackled this in your projects.
createOrder
method calls the services in right sequence, or that calculations like discounts, loyalty programs and taxes are calculated correctly? If the later, how come you just glossed it over in your example? You seem to be focusing on least important of your business logic.