C# 使用“动态”泛型和匿名对象“new{}”的模拟失败
Visual Studio 2019 Enterprise 16.9.4;最小起订量4.16.1;xunit 2.4.1;net5.0 我正在尝试对AlbumData.GetAlbumsAsync方法进行单元测试。我模拟了SqlDataAccess层,它在一个通用方法中使用Dapper调用DB 这是我的设置。模拟不起作用。在AlbumData.GetAlbumsAsync方法中,对模拟对象_sql.LoadDataAsync的调用返回null,并且输出设置为null 谁能告诉我我做错了什么 SqlDataAccess.csC# 使用“动态”泛型和匿名对象“new{}”的模拟失败,c#,generics,mocking,moq,C#,Generics,Mocking,Moq,Visual Studio 2019 Enterprise 16.9.4;最小起订量4.16.1;xunit 2.4.1;net5.0 我正在尝试对AlbumData.GetAlbumsAsync方法进行单元测试。我模拟了SqlDataAccess层,它在一个通用方法中使用Dapper调用DB 这是我的设置。模拟不起作用。在AlbumData.GetAlbumsAsync方法中,对模拟对象_sql.LoadDataAsync的调用返回null,并且输出设置为null 谁能告诉我我做错了什么 Sq
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;
}
}
AlbumData.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;
}
...
}
AlbumDataTest.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的建议,我更新了测试以使用它
还更新了@brent.reynolds fiddle以实际实施单元测试:
这一切都可以在小提琴中运行,但当我将完全相同的测试粘贴到AlbumDataTest中时,它仍然返回null。Assert.Nullactual;通过,Assert.Trueactual.Count==3;失败了
更新2:
我发布了一个测试失败的项目。
如果运行API.Library.ConsoleTests项目,模拟将正常工作。如果使用测试资源管理器运行API.Library.tests项目中的测试,模拟将失败
@brent.reynolds通过将动态通用对象更改为对象,使模拟得以运行。现在尝试调试动态问题
更新3:
如果我将AlbumData类移动到与AllbumDataTest类相同的项目中,模拟将使用动态返回包含三个对象的列表。但是当AlbumData类像在现实世界中一样位于单独的项目中时,mock返回null
我已经更新了存储库。我删除了console应用程序,并创建了一个包含两个场景的失败和通过文件夹
为什么将模拟传递到另一个项目中的类会使模拟失败
更新4:
见brent.reynolds接受的答案和我的评论。问题是在模拟设置中使用匿名对象。我已经删除了失败的模拟存储库和dotnetfiddle。使用它。IsAny而不是空字符串
sqlDataAccessMock.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>
(It.IsAny<string>(), new { }, It.IsAny<string>())).Returns(Task.FromResult(_albums));
注意:您不能在动态对象上使用它。IsAny使用它。IsAny代替空字符串
sqlDataAccessMock.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>
(It.IsAny<string>(), new { }, It.IsAny<string>())).Returns(Task.FromResult(_albums));
注意:您不能使用它。在动态对象上,它可能与异步方法有关。根据法律,你可以
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>(
It.IsAny<string>(),
new {},
It.IsAny<string>())
.Result)
.Returns(_albums);
执行部分说明/摘要:
在模拟设置中使用匿名对象new{}是中心问题。当测试类和被测试的类在同一个项目中时,它工作,但当它们在不同的项目中时,它不工作,因为这样它就不能被重用。IsAny将不起作用,因为编译器禁止LINQ表达式树中的动态。brent.reynolds使用对象解决了这个问题。它可能与异步方法有关。根据法律,你可以
sqlDataAccessMock
.Setup(d => d.LoadDataAsync<AlbumModel, dynamic>(
It.IsAny<string>(),
new {},
It.IsAny<string>())
.Result)
.Returns(_albums);
执行部分说明/摘要:
在模拟设置中使用匿名对象new{}是中心问题。当测试类和被测试的类在同一个项目中时,它工作,但当它们在不同的项目中时,它不工作,因为这样它就不能被重用。IsAny将不起作用,因为编译器禁止LINQ表达式树中的动态。brent.reynolds使用object解决了这个问题。你为什么认为这会有什么不同?您的更改是不需要的,它仍然返回null。我如何理解,当您的安装程序期望一个空字符串时,您不能传入dbo.spAlbum_GetAll?为什么您认为这会有任何不同?您的更改不需要,并且仍然返回null。我的理解是,当您的安装程序需要一个空字符串时,您不能传入dbo.spAlbum_GetAll,正如我向freeAll it解释的那样。IsAny是不需要的,也不能解决问题。它也与异步无关。我的代码与其他非泛型异步方法一起工作。可能不需要It.IsAny,但您应该传入所需的特定字符串,在本例中为dbo.spAlbum_GetAll和AlbumConnection,而不是空字符串。它是一个模拟的输入参数,超出了类型,IsAny的全部要点是只在类型上匹配,而忽略值。这就是你告诉最小起订量参数是无关的。我也不清楚为什么要在AlbumData.cs中指定动态类型参数。这似乎没有必要,因为您只是传入一个空对象。@freeAll是正确的,请参阅下面的示例,正如我向freeAll It解释的那样。IsAny不是必需的,也不能解决问题。它也与异步无关。我的代码与其他非泛型异步方法一起工作。可能不需要It.IsAny,但您应该传入所需的特定字符串,在本例中为dbo.spAlbum_GetAll和AlbumConnection,而不是空字符串。它是一个模拟的输入参数,超出了类型,IsAny的全部要点是只在类型上匹配,而忽略值。这就是你告诉最小起订量参数是无关的。我也不清楚为什么要指定动态类型
AlbumData.cs中的参数。这似乎没有必要,因为您只是传入一个空对象。@freeAll是正确的,请参见下面的示例
var output = await _sql.LoadDataAsync<AlbumModel, object>(
"dbo.spAlbum_GetAll",
new { },
"AlbumConnection");