.Net Core 单元测试错误 - 源 IQueryable 未实现 IAsyncEnumerable<...>
.Net Core Unit Test Error - The source IQueryable doesn't implement IAsyncEnumerable<...>
我有一行代码在单元测试中失败,但在开发和生产中运行良好。
var result = await _mapper.ProjectTo<GetApplicationsResponse.Application>(pipelineContext.Query).ToListAsync(cancellationToken);
pipelineContext.Query
是 IQueryable
.
的类型
我要进行的测试如下
[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<...>
,因为这是新方法\工作标准。
所以,如果能帮助我解决这个错误,我将不胜感激
System.InvalidOperationException : The source IQueryable doesn't implement IAsyncEnumerable<TQ.Applications.Application.Queries.GetApplications.GetApplicationsResponse+Application>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
---- 编辑 ---
之前忘记提到测试使用的是内存数据库
问题是 ToListAsync 想要一个实现 IAsyncEnumerable 的序列,但 ProjectTo 没有给它。
您正在使用 EntityFrameworkCore in-memory 提供程序,我假设您将其注入到 SUT 中并在失败时引用它。这是主要问题,因为 in-memory 提供程序不提供实现 IAsyncEnumerable 的序列。 ProjectTo 最终向 ToListAsync 提供了一个 IQueryable,这是行不通的。
至于如何解决,有两种方法。
- lazy/right 方法:使用更好的 DbContext。
以下 LINQPad 示例使用 EntityFrameworkCore.Testing.Moq 创建可注入的 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; }
}
这个returns:
显然,在没有最小可重现示例的情况下,我已经对此进行了简化,但这不应该改变方法。我假设该集合是基于 属性 名称(查询)的 read-only,因此使用 AddToReadOnlySource 进行排列。如果不是 read-only,您将改用 AddRange。
- 模拟映射器。
根据 JBogard 对该主题的评论,我大部分时间都使用真实的映射器。但是,您似乎愿意模拟它,您可以简单地将 ProjectTo 调用模拟为 return 所需的 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; }
}
结果:
这使用了 AsyncEnumerable class from EntityFrameworkCore.Testing,您可以按原样使用它,也可以根据需要将其作为您自己实施的基础。
我有一行代码在单元测试中失败,但在开发和生产中运行良好。
var result = await _mapper.ProjectTo<GetApplicationsResponse.Application>(pipelineContext.Query).ToListAsync(cancellationToken);
pipelineContext.Query
是 IQueryable
.
我要进行的测试如下
[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<...>
,因为这是新方法\工作标准。
所以,如果能帮助我解决这个错误,我将不胜感激
System.InvalidOperationException : The source IQueryable doesn't implement IAsyncEnumerable<TQ.Applications.Application.Queries.GetApplications.GetApplicationsResponse+Application>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
---- 编辑 ---
之前忘记提到测试使用的是内存数据库
问题是 ToListAsync 想要一个实现 IAsyncEnumerable 的序列,但 ProjectTo 没有给它。
您正在使用 EntityFrameworkCore in-memory 提供程序,我假设您将其注入到 SUT 中并在失败时引用它。这是主要问题,因为 in-memory 提供程序不提供实现 IAsyncEnumerable 的序列。 ProjectTo 最终向 ToListAsync 提供了一个 IQueryable
至于如何解决,有两种方法。
- lazy/right 方法:使用更好的 DbContext。
以下 LINQPad 示例使用 EntityFrameworkCore.Testing.Moq 创建可注入的 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; }
}
这个returns:
显然,在没有最小可重现示例的情况下,我已经对此进行了简化,但这不应该改变方法。我假设该集合是基于 属性 名称(查询)的 read-only,因此使用 AddToReadOnlySource 进行排列。如果不是 read-only,您将改用 AddRange。
- 模拟映射器。
根据 JBogard 对该主题的评论,我大部分时间都使用真实的映射器。但是,您似乎愿意模拟它,您可以简单地将 ProjectTo 调用模拟为 return 所需的 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; }
}
结果:
这使用了 AsyncEnumerable class from EntityFrameworkCore.Testing,您可以按原样使用它,也可以根据需要将其作为您自己实施的基础。