.net core 使用AutoMapper ProjectTo和Moq.EntityFrameworkCore进行单元测试

.net core 使用AutoMapper ProjectTo和Moq.EntityFrameworkCore进行单元测试,.net-core,automapper,moq,xunit,.net Core,Automapper,Moq,Xunit,我有一些类通过依赖注入接收DbContexts,我想测试它们。我使用AutoMapper的ProjectTo,因为我的实体通常比我从类返回的对象(dto)大得多。我真的很喜欢让AutoMapper调整我的查询,以便它只选择DTO中的字段 我一直在尝试使用Moq.EntityFrameworkCore来模拟我的DbContext。它工作得相对较好,但确实会导致AutoMapper ProjectTo()出现问题。我最终患上了残疾 显然,我对“测试AutoMapper”或我的DbContext不感兴

我有一些类通过依赖注入接收DbContexts,我想测试它们。我使用AutoMapper的ProjectTo,因为我的实体通常比我从类返回的对象(dto)大得多。我真的很喜欢让AutoMapper调整我的查询,以便它只选择DTO中的字段

我一直在尝试使用Moq.EntityFrameworkCore来模拟我的DbContext。它工作得相对较好,但确实会导致AutoMapper ProjectTo()出现问题。我最终患上了残疾

显然,我对“测试AutoMapper”或我的DbContext不感兴趣,我只想测试我的代码。但是,我无法测试我的代码,因为它在投影上崩溃了

这是一个极简主义的复制,使用AutoFixture将代码缩短了一点,我将所有内容都放到了一个文件中,这样任何人都可以轻松地自己尝试:

使用AutoFixture;
使用AutoFixture.AutoMoq;
使用自动制版机;
使用Microsoft.EntityFrameworkCore;
使用最小起订量;
使用Moq.EntityFrameworkCore;
使用System.Collections.Generic;
使用System.Threading.Tasks;
使用Xunit;
命名空间UnitTestEFMoqProjectTo
{
公共类MyBusinessFixture
{
私人IFixture_固定装置;
公共设施
{
_夹具=新夹具()
.自定义(新的AutoMoqCustomization());
var mockMapper=新的映射配置(cfg=>
{
AddProfile(新的MappingProfile());
});
var mapper=mockMapper.CreateMapper();
_fixture.Register(()=>mapper);
}
[事实]
公共异步任务DoSomething_with mock和projectto_ValidatesMyLogic()
{
//安排
var mockContext=new Mock();
mockContext.Setup(x=>x.myenties).ReturnsDbSet(新列表(_fixture.CreateMany(10));
_fixture.Register(()=>mockContext.Object);
var business=_fixture.Create();
//表演
待办公事;
//断言
断言。真(真);
}
}
公共类MyDbContext:DbContext
{
公共虚拟数据库集myenties{get;set;}
}
公共类MyEntity
{
公共int Id{get;set;}
公共字符串名称{get;set;}
公共字符串SomeProperty{get;set;}
公共字符串SomeOtherProperty{get;set;}
}
公共类MyDto
{
公共int Id{get;set;}
公共字符串名称{get;set;}
}
公共接口IMyBusiness
{
任务DoSomething();
}
公共类MyBusiness:IMyBusiness
{
私有只读MyDbContext\u MyDbContext;
专用只读IMapper\u映射器;
公共MyBusiness(MyDbContext MyDbContext,IMapper映射器)
{
_myDbContext=myDbContext;
_映射器=映射器;
}
公共异步任务DoSomething()
{
//我的程序的逻辑在这里,我想测试。
//查询投影和枚举
var projectedEntities=await _mapper.ProjectTo(_myDbContext.MyEntities.ToListAsync();
//这里是我的程序的一些逻辑,我想测试一下。
}
}
公共类映射配置文件:配置文件
{
公共映射配置文件()
{
CreateMap();
}
}
}
应输出以下错误:

Message: 
    System.InvalidCastException : Unable to cast object of type 'Moq.EntityFrameworkCore.DbAsyncQueryProvider.InMemoryAsyncEnumerable`1[UnitTestEFMoqProjectTo.MyEntity]' to type 'System.Linq.IQueryable`1[UnitTestEFMoqProjectTo.MyDto]'.
  Stack Trace: 
    ProjectionExpression.ToCore[TResult](Object parameters, IEnumerable`1 memberPathsToExpand)
    ProjectionExpression.To[TResult](Object parameters, Expression`1[] membersToExpand)
    Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Object parameters, Expression`1[] membersToExpand)
    Mapper.ProjectTo[TDestination](IQueryable source, Object parameters, Expression`1[] membersToExpand)
    MyBusiness.DoSomething() line 79
    MyBusinessFixture.DoSomething_WithMocksAndProjectTo_ShouldMap() line 39
你知道我如何继续使用AutoMapper进行预测,同时让单元测试工作吗

以下是我的项目文件内容供参考:


netcoreapp3.1
全部的
运行时间;建设;本地人;内容文件;分析仪;可传递的

前几天我遇到了类似的问题,但最终我能够运行我的单元测试

我使用
MockQueryable.Moq
()来模拟
DBSet
,您可能应该试试这个包,因为可能存在问题

所以我的代码看起来像:

    public class Project
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class ProjectDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    class GetProjectsHandler : IRequestHandler<GetProjectsRequest, GetProjectsResponse>
    {
        private readonly IMyDbContext context;
        private readonly IMapper mapper;

        public GetProjectsHandler(
            IMyDbContext context,
            IMapper mapper)
        {
            this.context = context;
            this.mapper = mapper;
        }

        public async Task<GetProjectsResponse> Handle(GetProjectsRequest request, CancellationToken cancellationToken)
        {
            IReadOnlyList<ProjectDto> projects = await context
                .Projects
                .ProjectTo<ProjectDto>(mapper.ConfigurationProvider)
                .ToListAsync(cancellationToken);

            return new GetProjectsResponse
            {
                Projects = projects
            };
        }
    }

公共类项目
{
公共int Id{get;set;}
公共字符串名称{get;set;}
}
公共类项目
{
公共int Id{get;set;}
公共字符串名称{get;set;}
}
类GetProjectsHandler:IRequestHandler
{
私有只读IMyDbContext上下文;
专用只读IMapper映射器;
公共GetProjectsHandler(
IMyDbContext上下文,
IMapper(映射器)
{
this.context=上下文;
this.mapper=mapper;
}
公共异步任务句柄(GetProjectsRequest请求、CancellationToken CancellationToken)
{
IReadOnlyList项目=等待上下文
.项目
.ProjectTo(mapper.ConfigurationProvider)
.ToListSync(取消令牌);
返回新的GetProjectsResponse
{
项目=项目
};
}
}
简化单元测试如下所示:

    public class GetProjectsTests
    {
        [Fact]
        public async Task GetProjectsTest()
        {
            var projects = new List<Project>
            {
                new Project
                {
                    Id = 1,
                    Name = "Test"
                }
            };
            var context = new Mock<IMyDbContext>();
            context.Setup(c => c.Projects).Returns(projects.AsQueryable().BuildMockDbSet().Object);

            var mapper = new Mock<IMapper>();
            mapper.Setup(x => x.ConfigurationProvider)
                .Returns(
                    () => new MapperConfiguration(
                        cfg => { cfg.CreateMap<Project, ProjectDto>(); }));


            var getProjectsRequest = new GetProjectsRequest();
            var handler = new GetProjectsHandler(context.Object, mapper.Object);
            GetProjectsResponse response = await handler.Handle(getProjectsRequest, CancellationToken.None);


            Assert.True(response.Projects.Count == 1);
        }
    }
公共类GetProjectsTests
{
[事实]
公共异步任务GetProjectsTest()
{
var项目=新列表
{
新项目
{
Id=1,
Name=“测试”
}
};
var context=newmock();
Setup(c=>c.Projects).Returns(Projects.AsQueryable().BuildMockDbSet().Object);
var mapper=newmock();
mapper.Setup(x=>x.ConfigurationProvider)
.返回(
()=>新的MapperConfiguration(
cfg=>{cfg.CreateMap();}));
var getProjectsRequest=新建getProjectsRequest();
var handler=new GetProjectsHandler(context.Object,mapper.Object);