You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

用Moq和NUnit编写ASP.NET Core Web API POST单元测试,求验证及优化方案

ASP.NET Core Web API POST操作单元测试验证与优化方案

当前单元测试的正确性分析

你的现有测试用例整体方向是正确的,但存在几个需要完善的细节:

1. 缺少重复ID校验的Mock设置

控制器中调用了GetProductByIdAsync来检查重复ID,但当前测试未对这个方法做Mock,执行时会返回默认值(null),虽然这次测试能通过,但属于未覆盖依赖逻辑的侥幸情况,必须补充Mock:

// Arrange阶段新增:Mock GetProductByIdAsync,确认当前ID不存在
_mockRepository.Setup(repo => repo.GetProductByIdAsync(3))
               .ReturnsAsync((Product)null);

2. 可补充的验证点

现有测试已经覆盖了核心场景,但还能增加更细致的验证:

  • 验证CreatedAtActionResult的ActionName是否为GetProduct
  • 验证返回的路由参数id是否匹配
  • 确保AddProductAsync只被调用一次

3. Logger依赖的Mock(可选)

如果控制器注入了ILogger,测试时需要Mock它避免空引用异常:

var mockLogger = new Mock<ILogger<ProductsController>>();
var _controller = new ProductsController(mockLogger.Object, _mockRepository.Object);

其他可行的单元测试方案

方案1:用Moq的Verify替代手动列表跟踪

不需要维护_products列表,直接用Moq的Verify方法验证方法调用情况,更简洁:

[Test]
public async Task AddProduct_ValidProduct_ReturnsCreatedResult()
{
    // Arrange
    var newProduct = new Product { Id = 3, Name = "Chocolate", Price = "15", Quantity = 8 };

    _mockRepository.Setup(repo => repo.GetProductByIdAsync(3))
                   .ReturnsAsync((Product)null);
    _mockRepository.Setup(repo => repo.AddProductAsync(newProduct))
                   .Returns(Task.CompletedTask);

    // Act
    var result = await _controller.PostProduct(newProduct);

    // Assert
    var createdResult = result.Result as CreatedAtActionResult;
    Assert.Multiple(() =>
    {
        Assert.That(createdResult, Is.Not.Null);
        Assert.That(createdResult.ActionName, Is.EqualTo(nameof(_controller.GetProduct)));
        Assert.That(createdResult.RouteValues["id"], Is.EqualTo(3));
        Assert.That(createdResult.Value, Is.EqualTo(newProduct));
    });

    // 验证AddProductAsync被正确调用一次
    _mockRepository.Verify(repo => repo.AddProductAsync(It.Is<Product>(p => 
        p.Id == 3 && p.Name == "Chocolate")), Times.Once);
}

方案2:覆盖错误分支场景

除了合法新增,必须测试控制器的错误处理逻辑:

  • 模型验证失败:
[Test]
public async Task AddProduct_InvalidModel_ReturnsBadRequest()
{
    // Arrange
    _controller.ModelState.AddModelError("Name", "Name is required");
    var invalidProduct = new Product { Id = 3 };

    // Act
    var result = await _controller.PostProduct(invalidProduct);

    // Assert
    Assert.That(result.Result, Is.InstanceOf<BadRequestObjectResult>());
}
  • 重复ID冲突:
[Test]
public async Task AddProduct_DuplicateId_ReturnsConflict()
{
    // Arrange
    var existingProduct = new Product { Id = 3, Name = "Existing Product" };
    var newProduct = new Product { Id = 3, Name = "Chocolate" };

    _mockRepository.Setup(repo => repo.GetProductByIdAsync(3))
                   .ReturnsAsync(existingProduct);

    // Act
    var result = await _controller.PostProduct(newProduct);

    // Assert
    var conflictResult = result.Result as ConflictObjectResult;
    Assert.That(conflictResult, Is.Not.Null);
    Assert.That(((dynamic)conflictResult.Value).message, Is.EqualTo("A product with the same ID already exists."));
}

方案3:用理论测试批量验证多组数据

如果需要测试多组合法/非法数据,可以用xUnit的Theory结合InlineData批量执行:

[Theory]
[InlineData(4, "Candy", "10", 5)]
[InlineData(5, "Biscuit", "20", 10)]
public async Task AddProduct_ValidProducts_ReturnsCreatedResult(int id, string name, string price, int quantity)
{
    // Arrange
    var newProduct = new Product { Id = id, Name = name, Price = price, Quantity = quantity };
    _mockRepository.Setup(repo => repo.GetProductByIdAsync(id))
                   .ReturnsAsync((Product)null);
    _mockRepository.Setup(repo => repo.AddProductAsync(newProduct))
                   .Returns(Task.CompletedTask);

    // Act
    var result = await _controller.PostProduct(newProduct);

    // Assert
    var createdResult = result.Result as CreatedAtActionResult;
    Assert.That(createdResult.Value as Product, Is.EqualTo(newProduct));
    _mockRepository.Verify(repo => repo.AddProductAsync(newProduct), Times.Once);
}

内容的提问来源于stack exchange,提问作者Ajinkya Patwardhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:02:13