我对xUnit相当陌生,我有一个应用程序,它使用工作单元模式来返回数据到一个服务,我试图对这个服务进行单元测试。
下面是服务代码。
public async Task<IEnumerable<CatDto>> GetActiveCatsAsync()
{
var catList = new List<CatDto>();
var catEFList = await _uow.Repository<Cat>().GetActiveCatsAsync();
catList.AddRange(catEFList.Select(c => new CatDto
{
CatId = c.CatId,
CatName = c.CatName,
DisplayOrd = c.DisplayOrd
}));
return catList;
}
这里是这个服务调用的扩展。
public static async Task<List<Cat>> GetActiveCatsAsync(this IRepository<Cat> repository)
{
return await repository.AsQueryableRepo().Items.Where(c =>c.IsActive == true).OrderBy(c=>c.DisplayOrd).ToListAsync();
}
}
这是扩展所使用的接口
internal static IQueryableRepository<T> AsQueryableRepo<T>(this IRepository<T> repository) where T : class
{
return (IQueryableRepository<T>)repository;
}
这里是具体的工作单元类
public class SQLUnitOfWork<TContext> : BaseUnitOfWork, ISQLUnitOfWork<TContext>
where TContext : IDbContext, IDisposable
{
public SQLUnitOfWork(TContext context) : base(context)
{
}
}
这是我目前进行的单元测试的情况。
[Fact]
public void GetActiveCatsAsync_ReturnsTypeIEnumerableCatDto()
{
var mockLogger = new Mock<ILogger<CatService>>();
var mockUoW = new Mock<ISQLUnitOfWork>();
var catServ = new CatService(mockLogger.Object, mockUoW.Object);
var result = catServ.GetActiveCatsAsync();
Assert.IsType<Task<IEnumerable<CatDto>>>(result);
}
这个测试实际上是通过了,但是很明显,"结果 "变量中没有数据 因为没有设置mocks。我也试着设置了。
var mockRepo = new Mock<IRepository<Cat>>();
但这并不能让我进入上面的扩展方法。它没有给我显示 "asQueryableRepo() "方法来提供一个返回值。所以我不知道如何模拟返回数据,这将是一个简单的Cat对象,在服务调用中显示了三个属性。我也尝试过使用xUnit的.ReturnAsync()和Task.FromResult()功能。
经过几次搜索,我尝试实现xUnit.DependencyInjection,但这需要在测试项目的Startup.cs文件中进行配置。我的测试项目(用Visual Studio xUnit项目模板创建)没有Startup.cs文件。难道我用错了模板?
我也见过类似下面使用的东西。
scope = new CustomWebApplicationFactory<Startup>().Server.Host.Services.CreateScope();
service = scope.ServiceProvider.GetRequiredService<IDashboardService>();
context = scope.ServiceProvider.GetRequiredService<DTBContext>();
这似乎也是引用了一个启动类 所以我也没能让这个方法成功。我是否需要重新开始一个包含Startup.cs文件夹的项目?还是说我可以不用上面的代码进行单元测试?我还在研究这个问题,但到目前为止,我找不到一种方法来模拟服务调用和工作单元元素的依赖关系。
任何建议或意见都非常感谢! 谢谢您
更新:我想我现在理解了xUnit.DependencyInjection,所以我添加了一个startup.cs类,并添加了他们GitHub页面上提到的引用。我还不得不安装Microsoft.Extensions.Hosting包来处理一个 "没有实现 "的错误,现在我的项目有了一个启动器,上面通过的测试现在也通过了。我希望现在我可以在测试项目中处理依赖关系,但我会发现。
你可以尝试使用 As
:
[Fact]
public async Task GetActiveCatsAsync_ReturnsTypeIEnumerableCatDto()
{
var mockLogger = new Mock<ILogger<CatService>>();
var mockRepo = new Mock<IRepository<Cat>>();
var cat = new Cat
{
IsActive = true
};
mockRepo
.As<IQueryableRepository<Cat>>()
.Setup(x => x.Items)
.Returns(x => new[] { cat }.AsQueryable);
var mockUoW = new Mock<ISQLUnitOfWork>();
mockUoW.Setup(x => x.Repository<Cat>()).Returns(mockRepo.Object);
var catServ = new CatService(mockLogger.Object, mockUoW.Object);
var result = await catServ.GetActiveCatsAsync();
var single = Assert.Single(result);
Assert.Equal(cat, single);
}
更新。
是的,测试实体框架是一个单独的问题。在这种情况下,我建议单独测试版本库,使用 InMemoroyDatabase
或其他什么东西,这就不适合拉到服务测试中去。
为了真正测试服务是否在做它应该做的事情,理想情况下,你应该像这样验证。
[Fact]
public async Task GetActiveCatsAsync_ReturnsTypeIEnumerableCatDto()
{
var mockLogger = new Mock<ILogger<CatService>>();
var mockRepo = new Mock<IRepository<Cat>>();
var mockUoW = new Mock<ISQLUnitOfWork>();
mockUoW.Setup(x => x.Repository<Cat>()).Returns(mockRepo.Object);
var catServ = new CatService(mockLogger.Object, mockUoW.Object);
var result = await catServ.GetActiveCatsAsync();
mockRepo.Verify(x => x.GetActiveCatsAsync(), Times.Once);
}
但你无法在扩展方法上做到这一点 But you won't be able to do this on an extension method. 不幸的是,你要测试的代码并不适合轻松地针对它编写纯单元测试。具体来说,让UoW暴露仓库是很麻烦的。如果让 ISQLUnitOfWork
有自己 GetActiveCatsAsync
方法,这样仓库就不可见了(泄漏的抽象)--这样你就可以直接在UoW上进行验证。
mockUoW.Verify(x => x.GetActiveCatsAsync(), Times.Once);