问题描述
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>(),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
来使 Mock 工作。现在正在尝试调试 dynamic
问题。
更新 3:
如果我将 AlbumData
类移动到与 AllbumDataTest
类相同的项目中,模拟工作(使用 dynamic
)返回包含三个对象的列表。但是当 AlbumData
类在一个单独的项目中时(就像在现实世界中一样)模拟返回 null。
我已更新 https://github.com/PerProjBackup/Failing-Mock 存储库。我删除了控制台应用程序并创建了一个 Failing 和 Passing 文件夹,其中包含两个场景。
为什么将模拟传递给不同项目的类会使模拟失败?
更新 4:
请参阅 brent.reynolds 接受的答案和我在那里的评论。问题是在模拟设置中使用匿名对象。我已经删除了 Failing-Mock 存储库和 dotnetfiddle。
解决方法
可能与异步方法有关。根据{{3}},您可以这样做
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel,dynamic>(
It.IsAny<string>(),new {},It.IsAny<string>())
.Result)
.Returns(_albums);
或
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel,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>()
将不起作用,因为编译器禁止在 LINQ 表达式树中使用 dynamic
。 brent.reynolds 使用 object
解决了这个问题。
使用 It.IsAny<string>()
代替空字符串
sqlDataAccessMock.Setup(d => d.LoadDataAsync<AlbumModel,dynamic>
(It.IsAny<string>(),It.IsAny<string>())).Returns(Task.FromResult(_albums));
注意:您不能在动态对象上使用 It.IsAny<T>()