使用 xunit .NET Core 3.1 和 Moq 的工作单元单元测试服务

Unit Testing Service with Unit of Work with xunit .NET Core 3.1 and Moq

作为 xUnit 的新手,我有一个应用程序使用工作单元模式 return 数据到服务,我正在尝试对该服务进行单元测试。

这里是服务代码:

 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;
    }

这里是具体的工作单元class:

 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);
    }

这个测试实际上通过了,但显然 "result" 变量中没有数据,因为没有为 mocks 设置。我也试过设置:

 var mockRepo = new Mock<IRepository<Cat>>();

但这不允许我使用上面的扩展方法。它没有向我显示 "asQueryableRepo()" 方法为其提供 return 值。所以我不确定如何模拟 return 数据,这将是一个简单的 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>();

这似乎也提到了一家初创公司 class,所以我也确实无法使这种方法发挥作用。我是否需要从包含 Startup.cs 文件夹的项目重新开始?或者我可以在没有它的情况下对上面的代码进行单元测试吗?我仍在研究这个,但到目前为止我找不到一种方法来模拟服务调用和工作单元元素的依赖关系。

非常感谢任何建议或建议!谢谢!

更新:我想我现在理解了 xUnit.DependencyInjection,所以我添加了 startup.cs class,并添加了在其 GitHub 页面上引用的参考资料。我还必须安装 Microsoft.Extensions.Hosting 包来处理 "does not have an implementation" 错误,现在我的项目启动了,上面通过的测试现在通过了。我希望我现在可以处理测试项目中的依赖关系,但我会找到答案。

您可以尝试使用 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);
}

更新:

是的,测试 Entity Framework 是一个单独的问题。在那种情况下,我会建议使用 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);
}

但是您将无法在扩展方法上执行此操作。不幸的是,您正在测试的代码并不适合针对它轻松编写纯单元测试。具体来说,让 UoW 公开存储库很麻烦。让 ISQLUnitOfWork 拥有自己的 GetActiveCatsAsync 方法会更容易,这样存储库就不会可见(泄漏抽象)——这样你就可以直接在 UoW 上进行验证:

mockUoW.Verify(x => x.GetActiveCatsAsync(), Times.Once);