【问题标题】:.Net Core Unit Test Error - The source IQueryable doesn't implement IAsyncEnumerable<...>.Net 核心单元测试错误 - 源 IQueryable 未实现 IAsyncEnumerable<...>
【发布时间】:2021-01-02 07:40:01
【问题描述】:

我有一行代码在单元测试中失败,但在开发和生产中运行良好。

var result = await _mapper.ProjectTo<GetApplicationsResponse.Application>(pipelineContext.Query).ToListAsync(cancellationToken);

pipelineContext.QueryIQueryable 的类型。

我正在尝试进行的测试如下

[Fact]
public async Task Handle_Success_Returns_GetApplicationsResponse()
{
    //Arrange
    var sut = CreateSut();

    _pipelineSteps
        .Setup(steps => steps.GetEnumerator())
        .Returns(() => new List<IPipelineStep<GetApplicationsContext>>
        {
            Mock.Of<IPipelineStep<GetApplicationsContext>>()
        }.GetEnumerator());

    _mapper.Setup(x => x.ConfigurationProvider)
        .Returns(
            () => new MapperConfiguration(
                cfg =>
                {
                    cfg.CreateMap<Entities.ApplicationsAggregate.Application, GetApplicationsResponse.Application>();
                    cfg.CreateMap<Entities.ApplicationsAggregate.SiteLocation, GetApplicationsResponse.SiteLocation>();
                    cfg.CreateMap<Entities.ApplicationsAggregate.SiteAddress, GetApplicationsResponse.SiteAddress>();
                }));

    //Act
    var result = await sut.Handle(new GetApplicationsRequest(), default);
    
    //Assert
    result.Should().BeOfType<GetApplicationsResponse>();
    _pipelineSteps.Verify(steps => steps.GetEnumerator(), Times.Once);
}

我在这方面的限制是我无法从_projectTo&lt;...&gt; 更改,因为这是新的工作方法\标准。

所以,如果能够通过此错误,我将不胜感激

System.InvalidOperationException:源 IQueryable 未实现 IAsyncEnumerable。只有实现 IAsyncEnumerable 的源才能用于实体框架异步操作。

---- 编辑 ---

之前忘了提到测试使用的是内存数据库

【问题讨论】:

  • 哪一行代码会抛出这个InvalidOperationException
  • @PeterCsala 在测试 sut.Handle 中,在代码中我在顶部引用的行
  • 可能是ToListAsync 电话。解决方案是确保您的映射器返回一个 IAsyncEnumerable 序列。可能还需要提供者的 AsyncQueryProvider 。您可以自己动手来执行此操作(实现 IAsyncEnumerable),或使用为您执行此操作的现有库。周围有几个,我维护 EntityFramework.Testing.Moq 应该这样做。
  • @rgvlee 你能举例说明我将如何在测试中实施你的解决方案吗

标签: c# unit-testing automapper moq .net-core-3.1


【解决方案1】:

问题在于 ToListAsync 想要一个实现 IAsyncEnumerable 的序列,但 ProjectTo 没有提供它。

您正在使用 EntityFrameworkCore 内存提供程序,我假设您将其注入 SUT 并在故障点被引用。这是主要问题,因为内存提供程序不提供实现 IAsyncEnumerable 的序列。 ProjectTo 最终向 ToListAsync 提供了一个 IQueryable ,但这是行不通的。

至于如何解决,有两种方法。

  1. 懒惰/正确的方法:使用更好的 DbContext。

以下 LINQPad 示例使用 EntityFrameworkCore.Testing.Moq 创建一个可注入的 DbContext,该 DbContext 生成 IAsyncEnumerable 序列:

void Main()
{
    var fixture = new Fixture();

    var dataEntites = fixture.CreateMany<DataEntity>();
    var expectedResult = dataEntites.Select(x => new BusinessEntity() { id = x.Id, code = x.Code });

    var mapper = new Mapper(new MapperConfiguration(x => x.AddProfile(new MappingProfile())));
    var pipelineContext = Create.MockedDbContextFor<PipelineContext>();
    pipelineContext.Entities.AddRangeToReadOnlySource(dataEntites);

    var sut = new SUT(mapper, pipelineContext);

    var actualResult = sut.Handle().Result;

    var compareLogic = new CompareLogic();
    compareLogic.Config.IgnoreObjectTypes = true;
    compareLogic.Config.IgnoreCollectionOrder = true;
    var comparisonResult = compareLogic.Compare(expectedResult, actualResult);
    Console.WriteLine($"Are the sequences equivalent: {comparisonResult.AreEqual}");
    Console.WriteLine(expectedResult);
    Console.WriteLine(actualResult);
}

public class SUT
{
    IMapper _mapper;
    PipelineContext _pipelineContext;

    public SUT(IMapper mapper, PipelineContext pipelineContext)
    {
        _pipelineContext = pipelineContext;
        _mapper = mapper;
    }

    public async Task<List<BusinessEntity>> Handle()
    {
        return await _mapper.ProjectTo<BusinessEntity>(_pipelineContext.Entities).ToListAsync();
    }
}

public class PipelineContext : DbContext
{
    public PipelineContext(DbContextOptions<PipelineContext> options) : base(options) { }

    public virtual DbSet<DataEntity> Entities { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<DataEntity>().HasNoKey().ToView(nameof(DataEntity));
    }
}

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<DataEntity, BusinessEntity>()
        .ForMember(d => d.id, o => o.MapFrom(s => s.Id))
        .ForMember(d => d.code, o => o.MapFrom(s => s.Code))
        .ReverseMap();
    }
}

public class DataEntity
{
    public Guid Id { get; set; }

    public string Code { get; set; }
}

public class BusinessEntity
{
    public Guid id { get; set; }

    public string code { get; set; }
}

这会返回:

显然,在没有最小可重复示例的情况下,我已经对此进行了简化,但这不应该改变方法。我假设该集基于属性名称(查询)是只读的,因此使用 AddToReadOnlySource 进行排列。如果它不是只读的,您可以改用 AddRange。

  1. 模拟映射器。

根据 JBogard 对该主题的评论,我大部分时间都使用真正的映射器。但是,您似乎愿意模拟它,您可以简单地模拟 ProjectTo 调用以返回所需的 IAsyncEnumerable 序列:

void Main()
{
    var fixture = new Fixture();
    
    var dataEntites = new AsyncEnumerable<DataEntity>(fixture.CreateMany<DataEntity>());
    var expectedResult = new AsyncEnumerable<BusinessEntity>(dataEntites.Select(x => new BusinessEntity() { id = x.Id, code = x.Code }));

    var mapperMock = new Mock<IMapper>();
    mapperMock.Setup(x => x.ProjectTo<BusinessEntity>(It.IsAny<IQueryable<DataEntity>>(), It.IsAny<object>())).Returns(expectedResult);
    var mapper = mapperMock.Object;

    var sut = new SUT(mapper);

    var actualResult = sut.Handle(dataEntites).Result;

    var compareLogic = new CompareLogic();
    compareLogic.Config.IgnoreObjectTypes = true;
    compareLogic.Config.IgnoreCollectionOrder = true;
    var comparisonResult = compareLogic.Compare(expectedResult, actualResult);
    Console.WriteLine($"Are the sequences equivalent: {comparisonResult.AreEqual}");
    Console.WriteLine(expectedResult);
    Console.WriteLine(actualResult);
}

public class SUT
{
    IMapper _mapper;

    public SUT(IMapper mapper)
    {
        _mapper = mapper;
    }

    public async Task<List<BusinessEntity>> Handle(IQueryable<DataEntity> entities)
    {
        return await _mapper.ProjectTo<BusinessEntity>(entities).ToListAsync();
    }
}

public class DataEntity
{
    public Guid Id { get; set; }

    public string Code { get; set; }
}

public class BusinessEntity
{
    public Guid id { get; set; }

    public string code { get; set; }
}

结果:

这使用来自EntityFrameworkCore.TestingAsyncEnumerable 类,您可以按原样使用它,也可以根据需要作为您自己实现的基础。

【讨论】:

  • 不是对答案的评论,而是为遇到类似错误消息的人提供更多解释的链接:expertcodeblog.wordpress.com/2018/02/19/…。基本上是 List myList = await Task.FromResult(myIQueryableResult.ToList());使用类型为 IQueryable 的 myIQueryableResult 解决了我的问题(短期),并且可能是非常糟糕的编码。
猜你喜欢
  • 1970-01-01
  • 2020-06-02
  • 1970-01-01
  • 1970-01-01
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 2017-04-07
  • 2011-09-29
相关资源
最近更新 更多