Integration Testing with AutoMapper fails to initi

2019-07-18 05:47发布

问题:

Frameworks & Packages

.NETCoreApp 1.1
Xunit 2.2.0
AutoMapper 6.0.2
Microsoft.AspNetCore.TestHost 1.1.1
Microsoft.NET.Test.Sdk 15.0.0

Integration Test

public class ControllerRequestsShould
{
    private readonly TestServer _server;
    private readonly HttpClient _client;

    public ControllerRequestsShould()
    {
        _server = new TestServer(new WebHostBuilder()
            .UseContentRoot(Constants.apiProjectRoot)
            .UseStartup<Startup>()
            .UseEnvironment(Constants.testingEnvironment));
        _client = _server.CreateClient();
        _client.BaseAddress = new Uri(Constants.localHostUri);
    }

    [Fact]
    public async Task CreateAnEntity()
    {
        // Arrange
        var entityForCreationDto = new entityForCreationDto { Code = "00001", Name = "Entity One" };
        var jsonContent = JsonConvert.SerializeObject(entityForCreationDto);
        var stringContent = new StringContent(jsonContent);
        stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

        // Act
        var response = await _client.PostAsync("/api/controller", stringContent);
        response.EnsureSuccessStatusCode();

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
}

Startup.cs

public class Startup
{
    public IConfigurationRoot Configuration { get; }

    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(setupAction =>
        {
            setupAction.ReturnHttpNotAcceptable = true;
            setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
            setupAction.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
        });

        // Db context configuration
        var connectionString = Configuration["ConnectionStrings:DefaultConnection"];
        services.AddDbContext<YourContext>(options =>
        {
            options.UseSqlServer(connectionString);
        });

        // Register services for dependency injection
        services.AddScoped<IYourRepository, YourRepository>();

        services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

        services.AddScoped<IUrlHelper, UrlHelper>(implementationFactory =>
        {
            var actionContext =
                implementationFactory.GetService<IActionContextAccessor>().ActionContext;
            return new UrlHelper(actionContext);
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();
        loggerFactory.AddDebug(LogLevel.Information);
        loggerFactory.AddNLog();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler(appBuilder =>
            {
                appBuilder.Run(async context =>
                {
                    var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
                    if (exceptionHandlerFeature != null)
                    {
                        var logger = loggerFactory.CreateLogger("Global exception logger");
                        logger.LogError(500,
                            exceptionHandlerFeature.Error,
                            exceptionHandlerFeature.Error.Message);
                    }

                    context.Response.StatusCode = 500;
                    await context.Response.WriteAsync("An unexpected fault happened.  Try again later");
                });
            });
        }

        Mapper.Initialize(cfg =>
        {
            cfg.CreateMap<DataStore.Entities.Entity, Models.EntityDto>();
            cfg.CreateMap<Models.EntityDto, DataStore.Entities.Entity>();
            cfg.CreateMap<Models.EntityForCreationDto, DataStore.Entities.Entity>();
            cfg.CreateMap<DataStore.Entities.Entity, Models.EntityForCreationDto>();
        });

        app.UseMvc();
    }

Problem
The integration test fails after the controller method is invoked:

var response = await _client.PostAsync("/api/controller", stringContent);

It fails because AutoMapper has not been initialised. The way I understood this was that since the TestServer has the UseStartup method, it should use all the services configured in the api Startup.cs class (the UseContentRoot is pointing to my api project root)

This clearly isn't happening. Could someone show me how I need to configure the TestServer so that the AutoMapper configuration is picked up correctly please?

回答1:

You should specify the assembly in the ConfigureServices method : var assembly = typeof(Program).GetTypeInfo().Assembly; services.AddAutoMapper(assembly); I'm using Automapper Modules, so the mapping config is picked up automatically by AutoMapper, but even then, you still need the above config.



回答2:

Or just use this line

 services.AddAutoMapper(typeof(Startup));

instead of

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

which is more clear and clean in my opinion



回答3:

Thank you, it's work for me. Additionally you can add configuration options like this.

var assembly = typeof(Program).GetTypeInfo().Assembly;

        services.AddAutoMapper(cfg =>
        {
            cfg.AllowNullDestinationValues = true;
            cfg.CreateMap<ApplicationUser, ApplicationUserView> ().IgnoreAllPropertiesWithAnInaccessibleSetter();}, assembly);