如何对try-catch块进行单元测试?

时间:2013-12-03 08:31:03

标签: unit-testing asp.net-mvc-4 mocking try-catch

如何对try-catch块进行单元测试? 我正在使用Moq作为单元测试对象的ASP.NET MVC 4.0。我想弄清楚如何覆盖try-catch块。

以下是我的控制器动作方法的屏幕截图,包括try-catch块:

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
    }
}

您可以看到,当我从test explorer启用代码覆盖率着色选项时,它会显示未覆盖的catch块。

以下是我的单元测试方法的屏幕截图:

[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");
    }
}

我想弄清楚如何在单元测试方法中覆盖try-catch块。

2 个答案:

答案 0 :(得分:3)

您当前的测试有几个问题

  • 您不是孤立地测试控制器(我相信您正在使用产品服务的真实实现)。您应该对正在测试的类的所有依赖项使用模拟。因此,您可以避免其他类错误的影响,并且您可以轻松地模拟SUT和依赖项之间的任何通信。您已经遇到了在抛出异常时无法重现场景的问题。
  • 您的测试中有条件逻辑,这使其不可重复。记住 - 测试应该总是产生相同的结果。
  • 你的前两个断言没有验证任何东西,因为测试会提前失败。如果结果为null,那么在访问NullReferenceException属性期间您将获得Model。如果模型不是ProductDetailModel类型,则会有强制转换异常。

因此,您应该模拟您的依赖项,设置控制器和模拟之间的通信,然后采取行动。您的测试看起来像(此处使用Moq库):

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();
}

如您所见,此处使用了模拟服务。您设置要返回的dto,并验证是否已调用服务。还要记住,在断言中,您先将期望值放在首位,然后再转为实际值。

因此,以下是如何使用模拟设置异常抛出:

[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);
}

设置服务模拟以抛出一些异常,然后使用ExpectedException属性来验证控制器是否从服务中重新抛出异常。

答案 1 :(得分:0)

您希望明确哪一行在try块中抛出异常。然后使用以下方法

Assert exception from NUnit to MS TEST

<强>更新 抱歉在火车上,所以我赶紧写下来。这是完整的答案

您的测试应该很简单,因为

    [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>()));
    }

我还会创建一个简单的帮助方法,正如我在

之前指出的那样
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;
    }
}

你的控制器):

    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;
        }
    }

有几点需要注意:

您的测试方法名称写得不好。避免包含“测试”方法名称,因为您知道它是单元测试。始终使用非常易读的测试方法名称。您的单元测试中有“if”语句。避免测试中的任何逻辑,这可能会导致测试中出现错误。你的测试应该“非常”简单。由于您要测试异常,因此您的断言无关紧要。它们可以在单独的单元测试中。