0

I am a bit new to unit testing with xUnit, and I have some problems with AutoMapper. I am getting the Mapper already initialized issue.

I am using Automapper 8.0.0., ASP.NET Core 2.2 and xUnit 2.4.1.

I am writing unit tests for my controllers. I have unit tests in 3 different classes. Each class looks basically like this:

/* Constructor */
public ControllerGetTests()
{
    /// Initialize AutoMapper
    AutoMapper.Mapper.Reset();
    MapperConfig.RegisterMaps();

    /* Some mocking code here using Moq */

    _controller = new MyController();
}


[Fact]
public async void Get_WhenCalled_ReturnsOkResult()
{
    // Act
    var okResult = await _controller.Get();

    // Assert
    Assert.IsType<OkObjectResult>(okResult);
}

/* etc. */

All three classes are similar and are basic tests for controllers. All controllers are using AutoMapper. I am using the same static class MapperConfig to register my mappings:

public static class MapperConfig
{
    public static void RegisterMaps()
    {
        AutoMapper.Mapper.Initialize(config =>
        {
            config.CreateMap<SomeClass, SomeClassViewModel>();    
            config.CreateMap<SomeClassViewModel, SomeClass>();

        });
    }
}

I call this method in the constructor of each of the 3 test classes. Before calling it, I call the Mapper.Reset() - some answers here suggest that: Automapper - Mapper already initialized error

In the Test Explorer in VS when I select one test class and choose "Run selected tests", they all pass. However, when I select the main "Run all", some tests fail with the message Mapper already initialized. And each time it is different tests in different classes that fail.

I assume that different threads are created for different methods, but they are all trying to initialize the same mapper instance which throws an error.

However, I am not sure where am I supposed to call the initialization in one (and only one) place and have that same initialization be used for all my test classes (like I do in Startup.cs Configure method).

Thanks in advance.

Mario Mucalo
  • 597
  • 5
  • 16
  • Race condition with shared resource. Also use `async Task` and not `async void` – Nkosi Apr 10 '19 at 15:44
  • `RegisterMaps` should only be called once. – Nkosi Apr 10 '19 at 15:46
  • @Nkosi - I agree that it is a race condition with shared resource. Do you have a suggestion where I can call the RegisterMaps so that it is called only once for all my tests in all my test classes? – Mario Mucalo Apr 10 '19 at 15:47
  • My suggestion would be to redesign/refactor to follow explicit dependency principle and not tightly couple to static implementation concerns. – Nkosi Apr 10 '19 at 15:49
  • @Nkosi - I agree, I'd love to RegisterMaps in one place and then use dependency injection. I am just not sure where and how to do it in the context of a xUnit Tests project. Do you have any suggestions for documentation? – Mario Mucalo Apr 10 '19 at 15:53
  • 1
    Take a look at the answer I provided here https://stackoverflow.com/a/39868221/5233410 – Nkosi Apr 10 '19 at 15:54
  • @Nkosi - thanks for that link, I'll try some ideas from there. Thanks for your help, mate! – Mario Mucalo Apr 10 '19 at 15:58
  • 2
    Hey @MarioMucalo, check this https://stackoverflow.com/questions/47241708/automapper-mapper-already-initialized-error/47552436#47552436 – Dmitry Pavlov Apr 10 '19 at 17:52
  • 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

1

Thanks to @Nkosi and @Dmitry Pavlov for their ideas.

What I ended up doing was:

1) Moving to instance API of the AutoMapper

This meant that the AutoMapper is now defined in Startup.cs in ConfigureServices method as:

public void ConfigureServices(IServiceCollection services)
{
    // Auto Mapper Configurations
    var mappingConfig = new MapperConfiguration(mc =>
    {
        mc.AddProfile(new MyMappingProfile());
    });
    IMapper mapper = mappingConfig.CreateMapper();
    services.AddSingleton(mapper);

    //...    
}

And injected into controllers like:

public class ItemsInstanceController : ControllerBase
{
    private readonly IItemService _itemService;
    private readonly IMapper _mapper;

    public ItemsInstanceController(IItemService itemService, IMapper mapper)
    {
        _itemService = itemService;
        _mapper = mapper;
    }

    //...
}

2) However, without spinning up a special test server, startup.cs methods are not run when tests are executed. So, for testing purposes, I ended up writing a small helper class implementing a singleton pattern on AutoMapper:

public class AutomapperSingleton
{
    private static IMapper _mapper;
    public static IMapper Mapper
    {
        get
        {
            if (_mapper == null)
            {
                // Auto Mapper Configurations
                var mappingConfig = new MapperConfiguration(mc =>
                {
                    mc.AddProfile(new MyMappingProfile());
                });

                IMapper mapper = mappingConfig.CreateMapper();
                _mapper = mapper;
            }

            return _mapper;
        }
    }
}

3) Now in my tests I just needed to create the controller like this:

controller = new ItemsInstanceController(itemServiceMock.Object, AutomapperSingleton.Mapper);

and the initialization was never run twice, only once on constructing the instance of the AutoMapper.

I have written a blog post where I go into much more details and explanations, so if you need more information, please go and read it.

Mario Mucalo
  • 597
  • 5
  • 16
  • 1
    https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection – Lucian Bargaoanu Jul 30 '19 at 00:01
  • Thanks, @LucianBargaonu - for this particular case and the "Mapper already initialized" error this is not really relevant, but I agree it is a cleaner way to add AutoMapper to a project. – Mario Mucalo Jul 31 '19 at 07:35
0

Lazy loading of a wrapper class which initializes AutoMapper in it's constructor also works in the following manner:

public class StaticDependencies
{
    public static Lazy<StaticDependencies> Initializer = new Lazy<StaticDependencies>();

    public StaticDependencies()
    {
        MapperConfig.RegisterMaps();
    }

    public void AssertSetup()
    {
        // No Op
    }
}

Then, in the constructor of your XUnit Test, simply refer to the static lazy loaded object:

public ControllerGetTests()
{
    /// Initialize AutoMapper
    StaticDependencies.Initializer.Value.AssertSetup();

    /* Some mocking code here using Moq */

    _controller = new MyController();
}