如何对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块。
答案 0 :(得分:3)
您当前的测试有几个问题
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”语句。避免测试中的任何逻辑,这可能会导致测试中出现错误。你的测试应该“非常”简单。由于您要测试异常,因此您的断言无关紧要。它们可以在单独的单元测试中。