Question

Hello everyone i am quite new to unit testing. My scenario: I had a product business logic layer have the following class and interface (product, productCategory, interface IProductRepository, ProductService) in the productservice methods like add/update/delete are implemented. I want to write a unit test for when I add a product. Below is my code, but I am not sure if I am doing it correctly.

Scenario to test
1) when I add a new product check if expire date is greater than current date
2) when I add a new product the price must be greater than 0
3) do not add a product where the delete flag is set to false
4) when I add a product category name should not be null/empty

I would really appreciate if someone can point me to the right direction specially when it comes to assertion. code

Save Method in ProductService Class

   public Boolean save(Product product)
   {
       Boolean flg = false;
       if ( (product.Expire > DateTime.Now && product.Price > 0 )
        && (product.flg =true))
       {

           flg = true;

       }
       return flg;
   }

unit test

[TestClass()]
public class ProductServiceTests
{
    ProductService Objproductservice;
    List<Product> ProductList;
    Product product1;
    Product product2;
    Product product3;
    DateTime time;
    Boolean flg;

    [TestInitialize]
    public void Setup()
    {
      flg = false;
      product1 = new Product { ProductId = 1, Name = "Ice Cream Ben&Jerry", Description = "Dairy", Expire = DateTime.Now.AddDays(20), DateModified = DateTime.Now, Price = 3.99, flg = true, CatID=2, CategoryName="Dairy" };
      product2 = new Product { ProductId = 1, Name = "Rice Basmati", Description = "Grocery", Expire = DateTime.Now.AddDays(20), DateModified = DateTime.Now, Price = 7.99, flg = false, CatID = 1002, CategoryName = "Grocery" };

    }


    [TestMethod]
    public void when_save_product_expire_date_greater_current_date()
    {

        Objproductservice = new ProductService();
        flg = Objproductservice.save(product1);

        Assert.IsTrue(product1.Expire > DateTime.Now);
        Assert.IsTrue(flg);
    }

    [TestMethod()]
    public void when_save_product_price_greater_than_zero()
    {

        Objproductservice = new ProductService();
        flg = Objproductservice.save(product1);

        Assert.IsTrue(product1.Price > 0);
        Assert.IsTrue(flg);
    }

    [TestMethod]
    public void do_not_save_product_if_delete_flg_isFalse()
    {

        Objproductservice = new ProductService();
        flg = Objproductservice.save(product2);

        Assert.IsFalse(product2.flg);
        Assert.IsFalse(flg);
    }

    [TestMethod]
    public void when_add_product_stock_level_increate()
    {

    }

    [TestMethod]
    public void when_save_product_categoryName_should_not_be_null()
    {

        Objproductservice = new ProductService();
        flg = Objproductservice.save(product1);

        string CategoryName = product1.CategoryName;
        Assert.IsTrue(CategoryName.Length > 0);
        Assert.IsTrue(flg);
    }

}
Was it helpful?

Solution

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top