.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
相关产品推荐
相关产品推荐

