如何反序列化控制器Action的OkResult?测试遇BSON序列化异常
为使用MongoDB数据访问层的API编写Mock的xUnit测试时,无法将控制器返回的OkResult解析为JSON/BSON,调试可见目标数据,但抛出BSON序列化异常:
MongoDB.Bson.BsonSerializationException : 序列化Microsoft.AspNetCore.Mvc.ObjectResult的Value属性时出错:类型System.Collections.Generic.List`1[[DataAccess.Models.CyberGood, DataAccess, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]未配置为此ObjectSerializer实例的允许类型。
相关代码如下:
CyberGood模型
namespace DataAccess.Models; [BsonIgnoreExtraElements] public class CyberGood { [BsonId] [BsonElement("id")] public ObjectId Id { get; set; } [BsonElement("name")] public string Name { get; set; } [BsonElement("description")] public string Description { get; set; } [BsonElement("insideOf")] public string? InsideOf { get; set; } [BsonElement("madeIn")] public string MadeIn { get; set; } [BsonElement("price")] public decimal Price { get; set; } [BsonElement("amount")] public int Amount { get; set; } }
控制器代码
[Route("api/[controller]")] [ApiController] public class CyberGoodController : ControllerBase { private readonly ICyberGoodService _cyberGoodService; public CyberGoodController(ICyberGoodService cyberGoodService) { _cyberGoodService = cyberGoodService; } [HttpGet] public async Task<IActionResult> GetAllCyberGoodAsync() { try { var cyberGoods = await _cyberGoodService.GetAllCyberGoodAsync(); return Ok(cyberGoods); } catch (Exception ex) { return BadRequest(ex.Message); } } }
原出错测试用例
[Fact] [Obsolete] public async Task GetAll_CyberGoods_ReturnsList() { //Arrange var mock = new Mock<ICyberGoodService>(); mock.Setup(d => d.GetAllCyberGoodAsync()).ReturnsAsync(GetTestCyberGoods()); var service = new CyberGoodController(mock.Object); var objectSerializer = new ObjectSerializer(type => ObjectSerializer.DefaultAllowedTypes(type) || type.FullName.StartsWith("DataAccess") || type.FullName.StartsWith("Microsoft.AspNetCore.Mvc.OkObjectResult")); BsonSerializer.RegisterSerializer(objectSerializer); //Act IActionResult result = await service.GetAllCyberGoodAsync(); var bsonRes = BsonSerializer.Deserialize<List<CyberGood>>(result.ToBson()); //Assert Assert.Equal(GetTestCyberGoods().Count, bsonRes.Count); }
无需通过BSON序列化/反序列化提取OkResult中的数据,直接将IActionResult转换为OkObjectResult,取出其Value属性并强转为目标类型即可,这是测试ASP.NET Core控制器返回值的标准方式:
修改后的测试用例
[Fact] public async Task GetAll_CyberGoods_ReturnsList() { //Arrange var mock = new Mock<ICyberGoodService>(); var testData = GetTestCyberGoods(); mock.Setup(d => d.GetAllCyberGoodAsync()).ReturnsAsync(testData); var controller = new CyberGoodController(mock.Object); //Act var result = await controller.GetAllCyberGoodAsync(); // 验证结果为OkObjectResult并转换类型 var okResult = Assert.IsType<OkObjectResult>(result); // 提取返回数据并验证类型 var returnedGoods = Assert.IsType<List<CyberGood>>(okResult.Value); //Assert Assert.Equal(testData.Count, returnedGoods.Count); // 可进一步验证每个元素的属性匹配 for (int i = 0; i < testData.Count; i++) { Assert.Equal(testData[i].Id, returnedGoods[i].Id); Assert.Equal(testData[i].Name, returnedGoods[i].Name); // 其他属性按需添加验证 } }
原代码出错原因
原代码尝试序列化整个OkObjectResult到BSON,但ObjectSerializer未允许List<CyberGood>类型的序列化——泛型列表的FullName以System.Collections.Generic.List开头,并不符合type.FullName.StartsWith("DataAccess")的规则,因此触发异常。直接提取OkObjectResult的Value则完全绕开了不必要的BSON序列化步骤,更高效且符合测试规范。
内容的提问来源于stack exchange,提问作者Dorialean

