using System.Collections.Generic; using NUnit.Framework; using Rhino.Mocks; namespace PricingCalculator { public interface IProduct { decimal BasePrice { get; } decimal SalesPrice { get; } decimal GoldLevelDiscount { get; } bool IsOnSale { get; } } public interface ICustomer { bool HasFixedDiscountAgreement { get; } decimal FixedDiscount { get; } bool IsGoldLevelCustomer { get; } } public class Pricer { private readonly List _calculators = new List { new GoldLevelPriceCalculator(), new SalePriceCalculator(), new FixedDiscountCalculator() }; public decimal GetPrice(IProduct product, ICustomer customer) { decimal price = product.BasePrice; foreach (IPriceCalculator calculator in _calculators) { price = calculator.CalculatePrice(price, customer, product); } return price; } } public interface IPriceCalculator { decimal CalculatePrice(decimal price, ICustomer customer, IProduct product); } public class GoldLevelPriceCalculator : IPriceCalculator { #region IPriceCalculator Members public decimal CalculatePrice(decimal price, ICustomer customer, IProduct product) { if (customer.IsGoldLevelCustomer) { price = price*(1 - product.GoldLevelDiscount); } return price; } #endregion } public class SalePriceCalculator : IPriceCalculator { #region IPriceCalculator Members public decimal CalculatePrice(decimal price, ICustomer customer, IProduct product) { if (product.IsOnSale && !customer.HasFixedDiscountAgreement) { price = product.SalesPrice < price ? product.SalesPrice : price; } return price; } #endregion } public class FixedDiscountCalculator : IPriceCalculator { #region IPriceCalculator Members public decimal CalculatePrice(decimal price, ICustomer customer, IProduct product) { if (customer.HasFixedDiscountAgreement) { price = price*(1 - customer.FixedDiscount); } return price; } #endregion } public class CalculatorTestBase : AssertionHelper where T : IPriceCalculator, new() { protected IPriceCalculator _calculator; protected ICustomer customer; protected Pricer pc; protected IProduct product; [SetUp] public void Initialize() { customer = MockRepository.GenerateMock(); product = MockRepository.GenerateMock(); pc = new Pricer(); _calculator = new T(); } protected void AssertExpectations() { product.VerifyAllExpectations(); customer.VerifyAllExpectations(); } } [TestFixture] public class SalePriceCalculatorTests : CalculatorTestBase { [Test] public void should_return_current_price_if_current_price_lower_than_sales_price() { product.Expect(x => x.IsOnSale).Return(true); decimal basePrice = 10M; decimal salePrice = 9M; product.Expect(x => x.SalesPrice).Return(salePrice); decimal currentPrice = 8M; decimal price = _calculator.CalculatePrice(currentPrice, customer, product); Expect(price, Is.EqualTo(currentPrice)); AssertExpectations(); } [Test] public void should_return_current_price_if_fixed_discount_customer() { product.Expect(x => x.IsOnSale).Return(true); customer.Expect(x => x.HasFixedDiscountAgreement).Return(true); decimal basePrice = 10M; decimal salePrice = 9M; product.AssertWasNotCalled(x => x.SalesPrice); decimal price = _calculator.CalculatePrice(basePrice, customer, product); Expect(price, Is.EqualTo(basePrice)); AssertExpectations(); } [Test] public void should_return_normal_price_if_not_on_sale() { product.Expect(x => x.IsOnSale).Return(false); decimal basePrice = 10M; decimal price = _calculator.CalculatePrice(basePrice, customer, product); Expect(price, Is.EqualTo(basePrice)); AssertExpectations(); } [Test] public void should_return_sale_price_if_on_sale() { product.Expect(x => x.IsOnSale).Return(true); decimal basePrice = 10M; decimal salePrice = 9M; product.Expect(x => x.SalesPrice).Return(salePrice); decimal price = _calculator.CalculatePrice(basePrice, customer, product); Expect(price, Is.EqualTo(salePrice)); AssertExpectations(); } } [TestFixture] public class GoldLevelPriceCalculatorTests : CalculatorTestBase { [Test] public void should_return_gold_member_discount_if_gold_member() { decimal basePrice = 10M; customer.Expect(x => x.IsGoldLevelCustomer).Return(true); decimal goldDiscount = .1M; product.Expect(x => x.GoldLevelDiscount).Return(goldDiscount); decimal price = _calculator.CalculatePrice(basePrice, customer, product); Expect(price, Is.EqualTo(basePrice*(1 - goldDiscount))); AssertExpectations(); } [Test] public void should_return_normal_price_if_not_gold_member() { customer.Expect(x => x.IsGoldLevelCustomer).Return(false); decimal basePrice = 10M; decimal price = _calculator.CalculatePrice(basePrice, customer, product); Expect(price, Is.EqualTo(basePrice)); AssertExpectations(); } } [TestFixture] public class FixedDiscountCalculatorTests : CalculatorTestBase { [Test] public void should_return_fixed_discount_price_if_fixed_discount() { decimal basePrice = 10M; customer.Expect(x => x.HasFixedDiscountAgreement).Return(true); decimal fixedDiscount = 0.1M; customer.Expect(x => x.FixedDiscount).Return(fixedDiscount); decimal price = _calculator.CalculatePrice(basePrice, customer, product); Expect(price, Is.EqualTo(basePrice*(1 - fixedDiscount))); AssertExpectations(); } [Test] public void should_return_normal_price_if_no_fixed_discount() { customer.Expect(x => x.HasFixedDiscountAgreement).Return(false); decimal basePrice = 10M; decimal price = _calculator.CalculatePrice(basePrice, customer, product); Expect(price, Is.EqualTo(basePrice)); AssertExpectations(); } } [TestFixture] public class TestPriceCalculator : AssertionHelper { #region Setup/Teardown [SetUp] public void Initialize() { customer = MockRepository.GenerateMock(); product = MockRepository.GenerateMock(); pc = new Pricer(); } #endregion private ICustomer customer; private IProduct product; private Pricer pc; [Test] public void should_provide_base_price_if_product_on_sale_but_customer_has_fixed_discount() { customer.Expect(x => x.IsGoldLevelCustomer).Return(false); customer.Expect(x => x.HasFixedDiscountAgreement).Return(true); decimal basePrice = 10M; decimal salesPrice = 9M; product.Expect(x => x.BasePrice).Return(basePrice); product.Expect(x => x.IsOnSale).Return(true); product.AssertWasNotCalled(x => x.SalesPrice); decimal calcPrice = pc.GetPrice(product, customer); Expect(calcPrice, Is.EqualTo(basePrice)); customer.VerifyAllExpectations(); product.VerifyAllExpectations(); } [Test] public void should_provide_fixed_discount_if_product_on_sale_and_customer_has_fixed_discount() { customer.Expect(x => x.IsGoldLevelCustomer).Return(false); customer.Expect(x => x.HasFixedDiscountAgreement).Return(true); decimal fixedDiscount = 2M; customer.Expect(x => x.FixedDiscount).Return(fixedDiscount); decimal basePrice = 10M; product.Expect(x => x.BasePrice).Return(basePrice); product.Expect(x => x.IsOnSale).Return(true); // Sales price accessor should not be called product.AssertWasNotCalled(x => x.SalesPrice); decimal calcPrice = pc.GetPrice(product, customer); Expect(calcPrice, Is.EqualTo(basePrice*(1 - fixedDiscount))); customer.VerifyAllExpectations(); product.VerifyAllExpectations(); } [Test] public void should_provide_gold_discount_if_product_not_on_sale_and_customer_is_gold() { customer.Expect(x => x.IsGoldLevelCustomer).Return(true); decimal basePrice = 10M; product.Expect(x => x.BasePrice).Return(basePrice); product.Expect(x => x.IsOnSale).Return(false); decimal goldDiscount = 1M; product.Expect(x => x.GoldLevelDiscount).Return(goldDiscount); decimal calcPrice = pc.GetPrice(product, customer); Expect(calcPrice, Is.EqualTo(basePrice*(1 - goldDiscount))); customer.VerifyAllExpectations(); product.VerifyAllExpectations(); } [Test] public void should_provide_gold_discount_price_price_if_product_on_sale_but_sale_price_greater_than_discounted_price() { customer.Expect(x => x.IsGoldLevelCustomer).Return(true); decimal basePrice = 10M; decimal salesPrice = 9M; product.Expect(x => x.BasePrice).Return(basePrice); product.Expect(x => x.SalesPrice).Return(salesPrice); product.Expect(x => x.IsOnSale).Return(true); decimal goldDiscount = 2M; product.Expect(x => x.GoldLevelDiscount).Return(goldDiscount); decimal calcPrice = pc.GetPrice(product, customer); Expect(calcPrice, Is.EqualTo(basePrice*(1 - goldDiscount))); customer.VerifyAllExpectations(); product.VerifyAllExpectations(); } [Test] public void should_provide_no_discount_if_product_not_on_sale_and_customer_not_gold() { customer.Expect(x => x.IsGoldLevelCustomer).Return(false); decimal basePrice = 10M; product.Expect(x => x.BasePrice).Return(basePrice); product.Expect(x => x.IsOnSale).Return(false); decimal calcPrice = pc.GetPrice(product, customer); Expect(calcPrice, Is.EqualTo(basePrice)); customer.VerifyAllExpectations(); product.VerifyAllExpectations(); } [Test] public void should_provide_sales_price_if_product_on_sale() { customer.Expect(x => x.IsGoldLevelCustomer).Return(false); decimal basePrice = 10M; decimal salesPrice = 9M; product.Expect(x => x.BasePrice).Return(basePrice); product.Expect(x => x.SalesPrice).Return(salesPrice); product.Expect(x => x.IsOnSale).Return(true); decimal calcPrice = pc.GetPrice(product, customer); Expect(calcPrice, Is.EqualTo(salesPrice)); customer.VerifyAllExpectations(); product.VerifyAllExpectations(); } } }