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

.NET Core 6中AWS DynamoDB控制器单元测试示例及疑问

问题描述

我按照教程实现了以DynamoDB为数据库的Web API,现在想要编写单元测试,但因为没使用仓储或仓储接口而毫无头绪。之前在另一篇帖子里了解到可以Mock IDynamoDBContext 接口,但还是不清楚后续操作。

我的控制器代码如下:

namespace FollowerService.Controllers;

[Route("api/[controller]")]
[ApiController]
public class FollowerController : ControllerBase
{
    private readonly IDynamoDBContext _context; // 用于操作DynamoDB
    private readonly IConfiguration _configuration;

    public FollowerController(IDynamoDBContext context, IConfiguration configuration)
    {
        _context = context;
        _configuration = configuration; 
    }

    [HttpGet]
    public async Task<IEnumerable<Follower>> GetAllFollowers(string userId)
    {
        var i = await _context.QueryAsync<Follower>(userId).GetRemainingAsync();
        return i;
    }


    [HttpPost]
    public async Task<IActionResult> AddFollower(Follower follower)
    {
        await _context.SaveAsync(follower);
        SQSProcessor sqsProcessor = new SQSProcessor(_configuration);
        await sqsProcessor.SQSPost(follower);
        return Ok(follower);
        
    }
}

请问能否提供一个测试Get或Post方法的优质单元测试示例?如果出于某些原因无需对该控制器编写单元测试,也请告知原因。


单元测试示例及说明

完全有必要给这个控制器写单元测试——单元测试可以验证控制器的业务逻辑是否符合预期,比如GetAllFollowers是否正确返回查询结果,AddFollower是否正确触发数据保存和消息发送(不过你的Post方法目前有个依赖硬编码的问题,后面会说明)。

我们用Moq框架来Mock依赖的接口,下面分两个方法给出测试示例:

1. 测试GetAllFollowers方法

using Moq;
using FollowerService.Controllers;
using Amazon.DynamoDBv2.DataModel;
using Xunit;

public class FollowerControllerTests
{
    private readonly Mock<IDynamoDBContext> _mockDynamoDbContext;
    private readonly Mock<IConfiguration> _mockConfiguration;
    private readonly FollowerController _controller;

    public FollowerControllerTests()
    {
        // 初始化Mock对象
        _mockDynamoDbContext = new Mock<IDynamoDBContext>();
        _mockConfiguration = new Mock<IConfiguration>();
        _controller = new FollowerController(_mockDynamoDbContext.Object, _mockConfiguration.Object);
    }

    [Fact]
    public async Task GetAllFollowers_ValidUserId_ReturnsExpectedFollowers()
    {
        // 准备测试数据
        var testUserId = "user123";
        var expectedFollowers = new List<Follower>
        {
            new Follower { UserId = testUserId, FollowerId = "follower456" },
            new Follower { UserId = testUserId, FollowerId = "follower789" }
        };

        // Mock QueryAsync和GetRemainingAsync的行为
        var mockAsyncSearch = new Mock<IAsyncSearch<Follower>>();
        mockAsyncSearch.Setup(s => s.GetRemainingAsync(It.IsAny<CancellationToken>()))
                       .ReturnsAsync(expectedFollowers);
        
        _mockDynamoDbContext.Setup(c => c.QueryAsync<Follower>(testUserId, It.IsAny<QueryOperationConfig>(), It.IsAny<CancellationToken>()))
                            .Returns(mockAsyncSearch.Object);

        // 执行测试方法
        var result = await _controller.GetAllFollowers(testUserId);

        // 验证结果
        Assert.Equal(expectedFollowers.Count, result.Count());
        Assert.Equal(expectedFollowers, result);
        
        // 验证DynamoDB的QueryAsync是否被正确调用
        _mockDynamoDbContext.Verify(c => c.QueryAsync<Follower>(testUserId, It.IsAny<QueryOperationConfig>(), It.IsAny<CancellationToken>()), Times.Once);
    }
}

2. 测试AddFollower方法(先优化代码)

你的AddFollower方法里直接new SQSProcessor(_configuration),这会导致无法Mock SQS的逻辑,单元测试会真的调用外部SQS服务,不符合单元测试隔离依赖的原则。所以首先要做代码优化:

第一步:给SQSProcessor抽象接口

public interface ISQSProcessor
{
    Task SQSPost(Follower follower);
}

public class SQSProcessor : ISQSProcessor
{
    private readonly IConfiguration _configuration;

    public SQSProcessor(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public async Task SQSPost(Follower follower)
    {
        // 原有SQS发送逻辑保持不变
    }
}

第二步:修改控制器,注入ISQSProcessor

public class FollowerController : ControllerBase
{
    private readonly IDynamoDBContext _context;
    private readonly ISQSProcessor _sqsProcessor;

    // 修改构造函数,注入ISQSProcessor
    public FollowerController(IDynamoDBContext context, ISQSProcessor sqsProcessor)
    {
        _context = context;
        _sqsProcessor = sqsProcessor; 
    }

    [HttpPost]
    public async Task<IActionResult> AddFollower(Follower follower)
    {
        await _context.SaveAsync(follower);
        await _sqsProcessor.SQSPost(follower);
        return Ok(follower);
    }
}

第三步:编写AddFollower的单元测试

[Fact]
public async Task AddFollower_ValidFollower_ReturnsOkResult()
{
    // 准备测试数据
    var testFollower = new Follower { UserId = "user123", FollowerId = "follower456" };

    // Mock SaveAsync的行为(只需验证调用,无需返回值)
    _mockDynamoDbContext.Setup(c => c.SaveAsync(testFollower, It.IsAny<CancellationToken>()))
                        .Returns(Task.CompletedTask);

    // 新增ISQSProcessor的Mock
    var mockSqsProcessor = new Mock<ISQSProcessor>();
    mockSqsProcessor.Setup(p => p.SQSPost(testFollower))
                    .Returns(Task.CompletedTask);

    // 重新初始化控制器,传入Mock的ISQSProcessor
    var controller = new FollowerController(_mockDynamoDbContext.Object, mockSqsProcessor.Object);

    // 执行测试方法
    var result = await controller.AddFollower(testFollower);

    // 验证返回结果是OkObjectResult,且数据正确
    var okResult = Assert.IsType<OkObjectResult>(result);
    var returnedFollower = Assert.IsType<Follower>(okResult.Value);
    Assert.Equal(testFollower.UserId, returnedFollower.UserId);
    Assert.Equal(testFollower.FollowerId, returnedFollower.FollowerId);

    // 验证依赖方法是否被正确调用
    _mockDynamoDbContext.Verify(c => c.SaveAsync(testFollower, It.IsAny<CancellationToken>()), Times.Once);
    mockSqsProcessor.Verify(p => p.SQSPost(testFollower), Times.Once);
}

关于是否需要写单元测试的说明

完全有必要编写单元测试:

  • 验证控制器逻辑是否符合预期,比如GetAllFollowers是否正确返回查询结果,AddFollower是否正确触发数据保存和消息发送
  • 隔离外部依赖(DynamoDB、SQS),避免测试依赖外部服务的可用性,加快测试速度
  • 后续代码重构时,单元测试可以保证原有逻辑不被破坏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 05:20:24