I would recommend you to follow Tell Don't Ask principle and instead of asking product for its expiration date, price etc - tell him to check itself (btw it's ok to have IsExpired, but consider to forbid setting invalid price to product):
public class Product
{
public DateTime Expire { get; set; }
public decimal Price { get; set; }
public virtual bool IsExpired
{
get { return Expire > DateTime.Now; }
}
public virtual bool IsValid
{
get { return !IsExpired && Price > 0; }
}
}
Now you can create different products expired or with invalid price and pass them to service :
ProductService service;
Mock<IProductRepository> repositoryMock;
Mock<Product> productMock;
[TestInitialize]
public void Setup()
{
repositoryMock = new Mock<IProductRepository>();
service = new ProductService(repositoryMock.Object);
productMock = new Mock<Product>();
}
[TestMethod]
public void Should_not_save_invalid_product()
{
productMock.SetupGet(p => p.IsValid).Returns(false);
bool result = service.save(productMock.Object);
Assert.False(result);
repositoryMock.Verify(r => r.Save(It.IsAny<Product>()),Times.Never());
}
[TestMethod]
public void Should_save_valid_product()
{
productMock.SetupGet(p => p.IsValid).Returns(true);
repositoryMock.Setup(r => r.Save(productMock.Object)).Returns(true);
bool result = service.save(productMock.Object);
Assert.True(result);
repositoryMock.VerifyAll();
}
Implementation of service will look like:
public bool save(Product product)
{
if (!product.IsValid)
return false;
return repository.Save(product);
}
Next write tests for Product to verify IsValid works correctly. You can use unit-testing framework which allows mocking static members (e.g. TypeMock), or you can use Factory to create products and inject ITimeProvider implementation to them. That will allow you to mock time provider. Or nice combined solution - create your own static time provider which allows to set value:
public static class TimeProvider
{
private static Func<DateTime> currentTime { get; set; }
public static DateTime CurrentTime
{
get { return currentTime(); }
}
public static void SetCurrentTime(Func<DateTime> func)
{
currentTime = func;
}
}
Use this provider instead of DateTime.Now
:
public virtual bool IsExpired
{
get { return Expire > TimeProvider.CurrentTime; }
}
Now you can provide current time and write tests for product:
private DateTime today;
[TestInitialize]
public void Setup()
{
today = DateTime.Today;
TimeProvider.SetCurrentTime(() => today);
}
[TestMethod]
public void Should_not_be_valid_when_price_is_negative()
{
Product product = new Product { Price = -1 };
Assert.False(product.IsValid);
}
[TestMethod]
public void Should_be_expired_when_expiration_date_is_before_current_time()
{
Product product = new Product { Expire = today.AddDays(-1) };
Assert.False(product.IsExpired);
}
// etc