简体   繁体   English

使用 Automapper 和 xUnit 进行单元测试时,Mapper 已初始化问题

[英]Mapper already initialized issue when using Automapper and xUnit for unit testing

I am a bit new to unit testing with xUnit, and I have some problems with AutoMapper.我对使用 xUnit 进行单元测试有点陌生,并且我在使用 AutoMapper 时遇到了一些问题。 I am getting the Mapper already initialized issue.我收到Mapper 已经初始化的问题。

I am using Automapper 8.0.0., ASP.NET Core 2.2 and xUnit 2.4.1.我正在使用 Automapper 8.0.0.、ASP.NET Core 2.2 和 xUnit 2.4.1。

I am writing unit tests for my controllers.我正在为我的控制器编写单元测试。 I have unit tests in 3 different classes.我在 3 个不同的类中有单元测试。 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.所有控制器都使用 AutoMapper。 I am using the same static class MapperConfig to register my mappings:我使用相同的静态类 MapperConfig 来注册我的映射:

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.我在 3 个测试类中的每一个的构造函数中调用此方法。 Before calling it, I call the Mapper.Reset() - some answers here suggest that: Automapper - Mapper already initialized error在调用它之前,我调用 Mapper.Reset() - 这里的一些答案表明: 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.在 VS 的测试资源管理器中,当我选择一个测试类并选择“运行选定的测试”时,它们都通过了。 However, when I select the main "Run all", some tests fail with the message Mapper already initialized .但是,当我选择主要的“全部运行”时,一些测试失败并显示消息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).但是,我不确定我应该在哪里(并且只有一个)地方调用初始化,并将相同的初始化用于我的所有测试类(就像我在 Startup.cs Configure 方法中所做的那样)。

Thanks in advance.提前致谢。

Thanks to @Nkosi and @Dmitry Pavlov for their ideas.感谢@Nkosi 和@Dmitry Pavlov 的想法。

What I ended up doing was:我最终做的是:

1) Moving to instance API of the AutoMapper 1) 转向 AutoMapper 的实例 API

This meant that the AutoMapper is now defined in Startup.cs in ConfigureServices method as:这意味着 AutoMapper 现在在 Startup.cs 中的 ConfigureServices 方法中定义为:

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. 2) 但是,如果没有启动特殊的测试服务器,则在执行测试时不会运行 startup.cs 方法。 So, for testing purposes, I ended up writing a small helper class implementing a singleton pattern on AutoMapper:因此,出于测试目的,我最终编写了一个在 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: 3)现在在我的测试中,我只需要像这样创建控制器:

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

and the initialization was never run twice, only once on constructing the instance of the AutoMapper.并且初始化从未运行过两次,仅在构造 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.我写了一篇博客文章,其中详细介绍了更多细节和解释,因此如果您需要更多信息,请阅读。

Lazy loading of a wrapper class which initializes AutoMapper in it's constructor also works in the following manner:延迟加载在其构造函数中初始化 AutoMapper 的包装类也以以下方式工作:

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:然后,在 XUnit 测试的构造函数中,只需引用静态延迟加载对象:

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

    /* Some mocking code here using Moq */

    _controller = new MyController();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM