首页 > 解决方案 > 在 .NET Core 3.1 中使用 AutoMapper、WebApplicationFactory、Entity Framework 和 DTO 进行集成测试

问题描述

我们有一个包含十几个集成测试的 API。在我添加了一些 DTO 并使用 AutoMapper 之前,所有测试都通过了。现在,所有测试使用 AutoMapper 和 DTO 的方法的测试都失败了。我已经提供了理解其中一个失败测试所需的所有代码。此外,我阅读了很多关于 AutoMapper 和以下 StackOverflow 帖子的内容:

  1. 使用 AutoMapper 进行集成测试无法初始化配置
  2. 一种在 ASP.NET Core 中的集成测试,使用 EF 和 AutoMapper

启动.cs

这是我们的 Startup.ConfigureServices()。我已经尝试了每个注释掉和/或标记为“尝试”的代码块。

 public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddDbContext<OurContext>(options =>
                options.UseSqlServer(Configuration["ConnectionString"]))
            .AddDbContext<OurContext>()
            .AddRazorPages()
            .AddMvcOptions(options => options.EnableEndpointRouting = false)
            .AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
        services
            .AddControllersWithViews();

        //ATTEMPTED
        //services
        //    .AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

        //ATTEMPTED
        //MapperConfiguration mapperConfiguration = new MapperConfiguration(mc =>
        //{
        //    mc.AddProfile(new OurProfile());
        //});
        //IMapper mapper = mapperConfiguration.CreateMapper();
        //services
        //    .AddSingleton(mapper);

        //ATTEMPTED
        //services
        //    .AddAutoMapper(typeof(Startup));

        //ATTEMPTED
        //var assembly = typeof(Program).GetTypeInfo().Assembly;
        //services
        //    .AddAutoMapper(assembly);

        //ATTEMPTED
        var assembly = typeof(Program).GetTypeInfo().Assembly;
        services.AddAutoMapper(cfg =>
        {
            cfg.AllowNullDestinationValues = true;
            cfg.CreateMap<OurModel, OurDto>()
                .IgnoreAllPropertiesWithAnInaccessibleSetter();
        }, assembly);
    }

控制器

这是我们的控制器。

[Route("api/[controller]")]
[ApiController]
public class OurController : ControllerBase
{
    private readonly OurContext _context;

    protected readonly ILogger<OurController> Logger;

    private readonly IMapper _mapper;

    public OurController(OurContext context, ILogger<OurController> logger, 
        IMapper mapper)
    {
        _context = context ??
            throw new ArgumentNullException(nameof(context));
        Logger = logger ??
                 throw new ArgumentNullException(nameof(logger));
        _mapper = mapper ??
                  throw new ArgumentNullException(nameof(mapper));
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<OurDto>>> GetAll()
    {

        IQueryable<OurModel> models = _context.OurModel;

        IQueryable<OurDto> dtos = 
            _mapper.Map<IQueryable<OurDto>>(models);

        return await dtos.ToListAsync();
    }
}

配置文件、模型和 DTO

轮廓

public class OurProfile : Profile
{
    public OurProfile()
    {
        CreateMap<OurModel, OurDto>();
    }
}

模型

public partial class OurModel
{
    public string Number { get; set; }
    public string Name1 { get; set; }
    public string Name2 { get; set; }
    public string Status { get; set; }
    public DateTime? Date { get; set; }
    public string Description { get; set; }
    public string Comment { get; set; }
    public string District { get; set; }
}

DTO

public class OurDto
{
    public string Number { get; set; }
    public string Name1 { get; set; }
    public string Name2 { get; set; }
    public string Status { get; set; }
    public DateTime? Date { get; set; }
    public string Description { get; set; }
    public string Comment { get; set; }
    public string District { get; set; }
}

测试夹具

这是我们的测试夹具。

public abstract class ApiClientFixture : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;

    protected abstract string RelativeUrl { get; }

    protected ApiClientFixture(WebApplicationFactory<Startup> factory)
    {
        _factory = factory;
    }

    protected HttpClient CreateClient()
    {
        HttpClient client;
        var builder = new UriBuilder();

        client = _factory.CreateClient();
        builder.Host = client.BaseAddress.Host;
        builder.Path = $"{RelativeUrl}";

        client.BaseAddress = builder.Uri;
        return client;
    }
}

测试

这是我们的测试课。此测试类中的单个测试失败。

public class Tests : ApiClientFixture
{
    public Tests(WebApplicationFactory<Startup> factory) : base(factory)
    {
    }

    protected override string RelativeUrl => "api/OurController/";

    [Fact]
    public async void GetAllReturnsSomething()
    {
        var response = await CreateClient().GetAsync("");

        Assert.True(response.IsSuccessStatusCode);
    }
}

当我调试测试时,我看到从提供给内存 API 的 URL 返回了 500 状态代码。

有人有什么建议吗?目前我们一半以上的测试都失败了,我怀疑 AutoMapper 没有为集成测试正确配置。

标签: entity-framework.net-coreautomapperdto

解决方案


创建地图IQueryable<T>并不是一个好的解决方案。在您的回答中,您正在失去适当的异步数据库查询流程。我在评论中写道IQueryable<T>,因为您正在寻找 500 错误原因。让它发挥作用是一回事,让它成为一个好的解决方案是另一回事。

我强烈建议使用 AutoMapperProjectTo()扩展,您可以直接在IQueryable<T>序列上使用它。它让您一次性将映射和查询结合起来。它或多或少会Select()根据您的映射进行操作,因此它不仅可以立即为您提供正确的查询结果模型,而且还可以减少从数据库中获取的列数,从而使查询运行得更快。但是,它当然有一些限制,例如您不能使用自定义类型转换器或条件映射。Project()您可以在文档中阅读更多信息。

用法:

public async Task<ActionResult<List<OurDto>>> GetAll()
{
    return await _context
        .OurModel
        .ProjectTo<OutDto>(_mapper.ConfigurationProvider)
        .ToListAsync();
}

推荐阅读