6

Error message

Message: System.InvalidOperationException : Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.

Application project

Define a mapping profile (ApplicationMappingProfile.cs)

public class ApplicationMappingProfile : Profile
{
    public ApplicationMappingProfile()
    {
        CreateMap<User, UserListDto>();
        CreateMap<Permission, PermissionListDto>();
        // add auto mapper mapping configurations here
    }
}

Register automapper services (ApplicationServiceCollectionExtensions.cs)

public static class ApplicationServiceCollectionExtensions
{
    public static IServiceCollection AddKodkodApplication(this IServiceCollection services)
    {
        services.AddAutoMapper();

        //todo: add conventional registrar
        services.AddTransient<IUserAppService, UserAppService>();
        services.AddTransient<IPermissionAppService, PermissionAppService>();

        return services;
    }
}

Unit test project

Create a test server to run Startup.cs (ApiTestBase.cs)

public class ApiTestBase : TestBase
{
    protected static HttpClient Client;

    public ApiTestBase()
    {
        //if this is true, Automapper is throwing exception
        ServiceCollectionExtensions.UseStaticRegistration = false;

        Client = GetTestServer();
    }

    private static HttpClient GetTestServer()
    {
        if (Client != null)
        {
            return Client;
        }

        var server = new TestServer(
            new WebHostBuilder()
                .UseStartup<Startup>()
                .ConfigureAppConfiguration(config =>
                {
                    config.SetBasePath(Path.GetFullPath(@"../../.."));
                    config.AddJsonFile("appsettings.json", false);
                })
        );

        return server.CreateClient();
    }
}

And test (AccountTests.cs).

public class AccountTests : ApiTestBase
{
    [Fact]
    public async Task TestAuthorizedAccessAsync()
    {
        var responseLogin = await LoginAsTestUserAsync();
        var responseContent = await responseLogin.Content.ReadAsAsync<OkObjectResult>();
        var responseJson = JObject.Parse(responseContent.Value.ToString());
        var token = (string)responseJson["token"];

        var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/test/GetUsers/");
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        var responseGetUsers = await Client.SendAsync(requestMessage);
        Assert.Equal(HttpStatusCode.OK, responseGetUsers.StatusCode);

        var users = await responseGetUsers.Content.ReadAsAsync<PagedList<UserListDto>>();
        Assert.True(users.Items.Count > 0);
    }
}

This test method calling /api/test/GetUsers/ method that is using an appservice (PermissionAppService.cs). This app service return a dto that is mapped from entity with using automapper. So exception is occuring here.

When I removed following line:

ServiceCollectionExtensions.UseStaticRegistration = false;

Then I am getting following error:

Message: System.InvalidOperationException : Mapper already initialized. You must call Initialize once per application domain/process.

This error occuring, because there are more than one classes that are inherited from ApiTestBase class. If I run only TestAuthorizedAccessAsync method, It runs with no issue.

The question can be a bit complicated, sorry for this :) feel free to ask where you don't understand.

PROJECT SOURCE

Edit

It works when I run web api project that uses appServices

Edit2

if I added following lines to TestBase constructor, only one test is failing and others passed.

public TestBase()
{
    lock (ThisLock)
    {
        Mapper.Reset();
        Client = GetTestServer();
    }

....

enter image description here

AliRıza Adıyahşi
  • 15,658
  • 24
  • 115
  • 197
  • Have you tried using the overload services.AddAutoMapper(typeof(Startup).Assembly);? – Sander Declerck Jun 06 '18 at 09:56
  • @SanderDeclerck, services.AddAutoMapper is in a seperate project. But yes I tried it(moved services.AddAutoMapper to web project) – AliRıza Adıyahşi Jun 06 '18 at 10:00
  • First of all, don't use the static mapping API. AM has an instance-based mapping API that's much easier to use, esp. when using dependency injection. – Gert Arnold Jun 20 '18 at 07:49
  • `ServiceCollectionExtensions.UseStaticRegistration = false;` should be right before `services.AddAutoMapper();`, otherwise it won't have effect. – Ivan Stoev Jun 20 '18 at 08:14
  • @IvanStoev it is called like you mentioned. But some tests are running as async, and `TestBase` constructor is running async, so `ServiceCollectionExtensions.UseStaticRegistration = false;` is not working properly. – AliRıza Adıyahşi Jun 20 '18 at 09:01
  • Why not use a `OneTimeSetup` or similar for your tests to initialize the mapper once, and then in your app intiialize it only if not already initialized? Or use DI to inject a class that does the mapper initialization as you do it now, and provide a second implementation that initializes it differently when run from tests. – BartoszKP Jun 22 '18 at 06:57
  • @BartoszKP, some example code would be appropriate :) if it is possible. – AliRıza Adıyahşi Jun 22 '18 at 08:50
  • @AliRızaAdıyahşi Unfortunately I cannot open your project (.NET Core newer than 2.0 is not supported on my VS 2017 - I'm probably missing some SDK or whatever) so I'm unable to verify if this really fixes your problem. – BartoszKP Jun 22 '18 at 09:03
  • @BartoszKP, I fixed the problem by using DI – AliRıza Adıyahşi Jun 22 '18 at 09:07
  • And thanks to @GertArnold – AliRıza Adıyahşi Jun 22 '18 at 09:07
  • 1
    @AliRızaAdıyahşi Consider self-answering your question then! Cheers! :) – BartoszKP Jun 22 '18 at 09:14
  • Does this answer your question? [Automapper - Mapper already initialized error](https://stackoverflow.com/questions/47241708/automapper-mapper-already-initialized-error) – Michael Freidgeim Sep 29 '21 at 12:48

2 Answers2

5
  • Disable automapper static registration and initialize it to pass as parameter to app service

TestBase.cs

public class TestBase
{
    private static Dictionary<string, string> _testUserFormData;

    protected readonly IMapper Mapper;
    protected readonly KodkodDbContext KodkodInMemoryContext;
    protected readonly ClaimsPrincipal ContextUser;
    protected readonly HttpClient Client;
    protected async Task<HttpResponseMessage> LoginAsTestUserAsync()
    {
        return await Client.PostAsync("/api/account/login",
            _testUserFormData.ToStringContent(Encoding.UTF8, "application/json"));
    }

    public TestBase()
    {
        // disable automapper static registration
        ServiceCollectionExtensions.UseStaticRegistration = false;

        // Initialize mapper
        Mapper = new Mapper(
            new MapperConfiguration(
                configure => { configure.AddProfile<ApplicationMappingProfile>(); }
            )
        );
        Client = GetTestServer();

        _testUserFormData = new Dictionary<string, string>
        {
            {"email", "testuser@mail.com"},
            {"username", "testuser"},
            {"password", "123qwe"}
        };

        KodkodInMemoryContext = GetInitializedDbContext();
        ContextUser = GetContextUser();
    }
...
  • Pass mapper as paramater to app service

UserApplicationServiceTests.cs

public class UserApplicationServiceTests : ApplicationTestBase
{
    private readonly IUserAppService _userAppService;

    public UserApplicationServiceTests()
    {
        var userRepository = new Repository<User>(KodkodInMemoryContext);
        _userAppService = new UserAppService(userRepository, Mapper);
    }
...
AliRıza Adıyahşi
  • 15,658
  • 24
  • 115
  • 197
1

TO avoid this error:

Message: System.InvalidOperationException : Mapper already initialized. You must call Initialize once per application domain/process.

You need to call Mapper.Reset() before executing each test:

The static Mapper.Initialize is intended to be called only once. To reset the static mapping configuration

Reset should not be used in production code. It is intended to support testing scenarios only.

You can call it at the beginning of the unit test but I recommend to do it in your unit test class default constrcutor like below:

public AccountTests()
{
    Mapper.Reset();
}
CodeNotFound
  • 22,153
  • 10
  • 68
  • 69
  • No luck! There are a lot of same advices. I tried all of them. I think there is a design problem with my code. BTW, even if your suggestion works, it's a workaround. – AliRıza Adıyahşi Jun 06 '18 at 10:06
  • It doesn't make sense to use a base test class. – AliRıza Adıyahşi Jun 06 '18 at 10:07
  • @AliRızaAdıyahşi I don't say anything about base test class. What do you mean by that? – CodeNotFound Jun 06 '18 at 12:58
  • Sorry, I mean, even if your suggestion works,your solution is not good. If I want to add dublicated lines, I didn't create a base class. Anyway, your workaround does not work. – AliRıza Adıyahşi Jun 06 '18 at 13:15
  • 1
    @AliRızaAdıyahşi This what is proposed by the documentation If it didn't work you might look how you write your test or even better, like you said on your first comment, redesign your code. – CodeNotFound Jun 06 '18 at 13:17
  • Actually there are a lot of proposed usage in documentations. @JimmyBogard (creator of automapper) suggest to use `ServiceCollectionExtensions.UseStaticRegistration = false;` in an issue. – AliRıza Adıyahşi Jun 06 '18 at 13:40
  • 1
    You are right, I should redesign code, but many of users in github say that `Mapper.Reset();` is not working solution as mine. Thanks for your effort to advice. – AliRıza Adıyahşi Jun 06 '18 at 13:42