首页 > 解决方案 > 使用 xunit .NET Core 3.1 和 Moq 的工作单元的单元测试服务

问题描述

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

该测试实际上通过了,但显然“结果”变量中没有数据,因为没有设置模拟。我也尝试设置:

 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 包来处理“没有实现”错误,现在我的项目启动了,上面通过的测试现在通过了。我希望我现在可以处理测试项目中的依赖关系,但我会发现的。

标签: c#unit-testingmoqxunitunit-of-work

解决方案


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

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

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

推荐阅读