Pawan
Pawan

Reputation: 2218

How to unit test try-catch block?

How to do unit test for try-catch block? I am working on ASP.NET MVC 4.0 with Moq as unit test object. And I want to figure out how to cover the try-catch block.

Below is the screen shot for my controller's action method including try-catch block:

public ActionResult ProductDelete(int id)
{
    try
    {
        ViewBag.Title = "Delete Product";
        ProductDetailDto dto = ProductService.GetProductDetail(id);
        return View("ProductDelete", BuildProductDetailModel(dto));
    }
    catch (Exception)
    {
        throw; // cannot test this line
    }
}

You can see that when I enable the code coverage coloring option from test explorer then it's showing the catch block uncovered.

Below is the screen shot for my unit test method:

[TestMethod]
public void ProductDeleteTest()
{            
    ProductController controller = new ProductController();
    var existingProductDetail = _ProductService.GetAllProductsDetails().ToList().FirstOrDefault();

    if (existingProductDetail != null)
    {
        TestControllerBuilder builder = new TestControllerBuilder();
        builder.InitializeController(controller);
        // Act
        var actual = controller.ProductDelete(existingProductDetail.ProductDetailId) as ViewResult;
        var viewModel = (ProductDetailModel)actual.Model;
        // Assert
        Assert.IsNotNull(actual);
        Assert.IsInstanceOfType(viewModel, typeof(ProductDetailModel));
        Assert.AreEqual(actual.ViewName, "ProductDelete");
    }
}

And I want to figure out how I can cover the try-catch block in unit testing method.

Upvotes: 2

Views: 8947

Answers (2)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236268

Your current test has several problems

  • You are not testing controller in isolation (I believe you are using real implementation of product service). You should use mocks for all dependencies of class you are testing. Thus you can avoid influence of other class bugs, and you can easily mock any communication between SUT and dependencies. You already faced problem where you can't reproduce scenario when exception is thrown.
  • You have conditional logic in your test, which makes it non-repeatable. Remember - test should always produce same results.
  • Your first two assertions are not verifying anything, because test will fail earlier. If result is null then you will have NullReferenceException during accessing Model property. And if model is not of ProductDetailModel type, you will have cast exception.

So, you should mock your dependencies, setup communication between your controller and mocks, and then act. Your test can look like (Moq library is used here):

private ProductController controller;
private Mock<IProductService> productServiceMock;
private Random random = new Random();

[TestInitialize]
public void Init()
{
    productServiceMock = new Mock<IProductService>();
    controller = new ProductController(productServiceMock.Object);
}

[TestMethod]
public void ShouldReturnDetailsOfDeletedProduct()
{
    int id = random.Next();
    var dto = new ProductDetailDto { ProductDetailId = id };
    productServiceMock.Setup(s => s.GetProductDetail(id)).Returns(dto);

    // Act
    var actual = controller.ProductDelete(id) as ViewResult;
    var viewModel = (ProductDetailModel)actual.Model;
    // Assert
    Assert.AreEqual("ProductDelete", actual.ViewName);
    Assert.AreEqual(id, viewModel.Id);
    productServiceMock.VerifyAll();
}

As you can see, mocked service is used here. You setup which dto to return, and you verify if service was called. Also keep in mind, that in assertion you put expected value first, and then goes actual value.

So, here is how you can setup exception throw with mocks:

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ShouldNotReturnResultWhenExceptionOccurs()
{
    int id = random.Next();
    var dto = new ProductDetailDto { ProductDetailId = id };
    productServiceMock.Setup(s => s.GetProductDetail(id))
                      .Throws(new ArgumentException());
    // Act
    controller.ProductDelete(id);
}

Setup service mock to throw some exception, and then use ExpectedException attribute to verify your controller re-thrown exception from service.

Upvotes: 4

Spock
Spock

Reputation: 6992

You want to be explicit on which line throw the exception in the try block. Then use the below approach

Assert exception from NUnit to MS TEST

UPDATE: Sorry was in the train so I was hurry to write it down. Here is complete answer

Your test should be simple as this

    [TestMethod]
    public void ProductDeleteAction_GetProductDetail_ThrowsException()
    {
        // Arrange
        var productServiceMock = new Mock<IProductService>();
        productServiceMock.Setup(x => x.GetProductDetail(It.IsAny<int>())).Throws(new Exception("some"));
        var sut = new ProductController(productServiceMock.Object);

        // Act
        AssertException.Throws<Exception>(() => sut.ProductDelete(It.IsAny<int>()));
    }

I would also create a simple helper method as I pointed out before

public static class AssertException
{
    public static T Throws<T>(Action action) where T : Exception
    {
        try
        {
            action();
        }
        catch (T ex)
        {
            return ex;
        }

        Assert.Fail("Expected exception of type {0}.", typeof(T));

        return null;
    }
}

Your Controller):

    public ProductController(IProductService productService) {
        _productService = productService;
    }

    public ActionResult ProductDelete(int id)
    {
        try
        {
            ViewBag.Title = "Delete Product";
            ProductDetailDto dto = _productService.GetProductDetail(id);
            return View("ProductDelete", BuildProductDetailModel(dto));

        }
        catch (Exception)
        {
            throw;
        }
    }

There are few things to note:

Your test method name is poorly written. Avoid including "Test" method name as you know it is a Unit Test. Always use a very readable test method name. You have an "if" statement in your Unit Test. Avoid any logic in your test, this can cause bugs in your test. Your tests should be 'very' simple. Since you are want to test the exception, your asserts are irrelevant. They can be in a separate Unit test.

Upvotes: 0

Related Questions