c# - 使用“动态”通用和匿名对象“新 {}”模拟失败
问题描述
Visual Studio 2019 企业版 16.9.4;最小起订量 4.16.1;xunit 2.4.1;net5.0
我正在尝试对我的AlbumData.GetAlbumsAsync()
方法进行单元测试。SqlDataAccess
我在一个generic
方法中使用 Dapper 来模拟调用数据库的层。
这是我的设置。模拟不起作用。在该AlbumData.GetAlbumsAsync()
方法中,对模拟对象 () 的调用_sql.LoadDataAsync
返回 null 并output
设置为 null。
谁能告诉我我错了什么?
SqlDataAccess.cs
public async Task<List<T>> LoadDataAsync<T, U>(string storedProcedure,
U parameters, string connectionStringName)
{
string connectionString = GetConnectionString(connectionStringName);
using (IDbConnection connection = new SqlConnection(connectionString))
{
IEnumerable<T> result = await connection.QueryAsync<T>(storedProcedure, parameters,
commandType: CommandType.StoredProcedure);
List<T> rows = result.ToList();
return rows;
}
}
专辑数据.cs
public class AlbumData : IAlbumData
{
private readonly ISqlDataAccess _sql;
public AlbumData(ISqlDataAccess sql)
{
_sql = sql;
}
public async Task<List<AlbumModel>> GetAlbumsAsync()
{
var output = await _sql.LoadDataAsync<AlbumModel, dynamic>
("dbo.spAlbum_GetAll", new { }, "AlbumConnection");
return output;
}
...
}
专辑数据测试.cs
public class AlbumDataTest
{
private readonly List<AlbumModel> _albums = new()
{
new AlbumModel { Title = "Album1", AlbumId = 1 },
new AlbumModel { Title = "Album2", AlbumId = 2 },
new AlbumModel { Title = "Album3", AlbumId = 3 }
};
[Fact]
public async Task getAlbums_returns_multiple_records_test()
{
Mock<ISqlDataAccess> sqlDataAccessMock = new();
sqlDataAccessMock.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>
(It.IsAny<string>(), new { }, It.IsAny<string>()))
.Returns(Task.FromResult(_albums));
AlbumData albumData = new AlbumData(sqlDataAccessMock.Object);
List<AlbumModel> actual = await albumData.GetAlbumsAsync();
Assert.True(actual.Count == 3);
}
...
}
更新1:
按照@freeAll 和@brent.reynolds 的建议,我更新了要使用的测试It.IsAny<string>()
还更新了@brent.reynolds fiddle 以实际实现单元测试:
https://dotnetfiddle.net/nquthR
这一切都在小提琴中起作用,但是当我将完全相同的测试粘贴到我的中AlbumDataTest
时,它仍然返回 null。 Assert.Null(actual);
通过,Assert.True(actual.Count == 3);
失败。
更新2:
我已经将一个测试失败的项目发布到https://github.com/PerProjBackup/Failing-Mock。如果您运行 API.Library.ConsoleTests 项目,则 Mock 可以工作。如果您在 API.Library.Tests 项目中使用Test Explorer
Mock 运行测试会失败。
@brent.reynolds 能够通过将dynamic
泛型更改为object
. 现在尝试调试dynamic
问题。
更新3:
如果我将该AlbumData
类移动到与该类相同的项目中,AllbumDataTest
则模拟工作(使用dynamic
)返回具有三个对象的列表。但是当AlbumData
类在一个单独的项目中时(就像在现实世界中一样),模拟返回 null。
我已经更新了https://github.com/PerProjBackup/Failing-Mock存储库。我删除了控制台应用程序并创建了一个包含两种方案的 Failing 和 Passing 文件夹。
为什么将模拟传递给不同项目的类会使模拟失败?
更新4:
请参阅 brent.reynolds 接受的答案和我的评论。问题是在模拟设置中使用了匿名对象。我已经删除了 Failing-Mock 存储库和 dotnetfiddle。
解决方案
它可能与异步方法有关。根据异步方法的文档,您可以这样做
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>(
It.IsAny<string>(),
new {},
It.IsAny<string>())
.Result)
.Returns(_albums);
或者
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>(
It.IsAny<string>(),
new {},
It.IsAny<string>()))
.ReturnsAsync(_albums);
编辑:尝试添加It.IsAny<object>()
到设置:
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel, object>(
It.IsAny<string>(),
It.IsAny<object>(),
It.IsAny<string>()))
.Returns(Task.FromResult(_albums));
并将类型参数更改GetAlbumsAsync()
为:
var output = await _sql.LoadDataAsync<AlbumModel, object>(
"dbo.spAlbum_GetAll",
new { },
"AlbumConnection");
操作说明/摘要:
new {}
在模拟设置中使用匿名对象是核心问题。当测试类和被测试的类在同一个项目中时它起作用,但当它们在不同的项目中时不起作用,因为它不能被重用。It.IsAny<dynamic>()
将不起作用,因为编译器禁止dynamic
在 LINQ 表达式树内。brent.reynolds 的使用object
解决了这个问题。
推荐阅读
- node.js - 如何过滤MongoDB中数组中指定类型的数据?
- python - 如何根据 shell 脚本中另一个命令的输出运行命令?
- c# - 如何在 IdentityServer 4 中设置自定义错误消息?
- r - 为 DT 输出渲染文本输入时出现 R 闪亮错误
- python-3.x - 在 Python 中读取文本文件中的值并存储到数组中
- ios - 改进这个简单的 Swift 函数的时间检查,涉及整数到双精度转换和除法
- sqlite - SQLite 和 react-native
- reactjs - 如何访问元素中子元素的类型值
- javascript - 找不到类的第 n 个孩子或第 n 个类型
- java - 如何在android studio中基于Wifi路由器显示MAC地址和RSSI信息?