如何使用AutoMapper模拟列表转换 [英] How to Mock a list transformation using AutoMapper

查看:231
本文介绍了如何使用AutoMapper模拟列表转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用AutoMapper,并且将映射引擎定义为

private readonly IMappingEngine _mappingEngine;

我通过构造函数注入对其进行初始化,并在以下代码中使用

var product=//Get a single product
var productModel = _mappingEngine.Map<ProductModel>(product);

上面的方法很完美.现在,我需要将Product的列表映射到ProductModel的列表.控制器操作中的以下工作

var entities =//Get list of products
var model = entities.Select(e => _mappingEngine.Map<ProductModel>(e));

上面的LINQ代码使用foreach并将每个Product转换为一个ProductModel现在,我需要对上面的代码进行单元测试,但无法使用Moq来模拟上面的LINQ语句

我尝试了以下

var mapper = new Mock<IMappingEngine>();
mapper.Setup(m => m.Map<ProductModel>(product)).Returns(ProductModel);

以上映射器设置适用于单个对象映射.我们如何使用产品列表设置以上内容

所以,我希望能够像这样设置Product的列表:

var productList=new List<Product>{new Product{Id=1,name="product 1"},
                                  new Product{Id=2,name="product 2"}};

并定义一个模拟,该模拟将返回ProductModel的列表,如下所示:

var productModelList=new List<ProductModel>{new ProductModel{Id=1,name="product 1"},
                                            new ProductModel{Id=2,name="product 2"}};  

当我的测试调用控制器时(使用模拟IMappingEngine转换列表)

var model = entities.Select(e => _mappingEngine.Map<ProductModel>(e));

因此,在编写Moq单元测试时,如何设置以上内容,以便将productList作为输入并返回productModelList_mappingEngine.Map?

解决方案

首先,您真的是否需要模拟从ProductProductModel的映射,或者仅仅是有效地创建IMappingEngine的实例并将其提供给您的控制器,而不是Mock.Object.

在测试设置中添加映​​射很简单:

Mapper.CreateMap<Product, ProductModel>();

并清除测试拆解中的映射:

Mapper.Reset();

将允许您在测试中仅向控制器构造函数提供Mapper.Engine.显然,这取决于您的测试方法,但是对于诸如AutoMapper这样的实用且可靠且几乎没有时间开销的东西来说,这可能是一种有效的方法.

假设您确实想模拟映射,则可以使用回调为每个调用返回不同的项目,如下所示:

// Create a list of the mapped values you're expecting
var productModels = new List<ProductModel> {
    new ProductModel { Id=11,name="eleven"},
    new ProductModel { Id=12,name="twelve"},
    new ProductModel { Id=13,name="thirteen"}
};

// Mock the IMappingEngine
var engine = new Mock<IMappingEngine>();

// Create a variable to count the calls
var calls=0;

// Mock ALL calls to map, where the destination is ProductModel
// and source is Product
engine.Setup(m => m.Map<ProductModel>(It.IsAny<Product>()))
      .Returns(()=>productModels[calls]) // Return next productModel
      .Callback(()=>calls++)   // Increment counter to point to next model

您应该注意到,模拟Setup在模拟单独的映射,而不是将Product的列表映射到ProductModel的列表.那是因为您对entities.Select(e => _mappingEngine.Map<ProductModel>(e))的调用一次遍历列表中的一个项目,而不是要求映射引擎(或您的模拟对象)一次映射列表...

如果需要在模拟中更加精确,则还可以扩展Callback以验证是否已映射期望的Product.您可能不想每次都这样做,但是在某些情况下它可能会很有用.因此,您可以执行以下操作:

// Declare a list of expected products to map from
var products = new List<Product> { 
    new Product {Id=1, name="One"},
    new Product {Id=2, name="Two"},
    new Product {Id=3, name="Three"}
};


engine.Setup(m => m.Map<ProductModel>(It.IsAny<Product>()))
      .Returns(() => productModels[calls])
      .Callback<Product>(x => {Assert.AreEqual(x.Id, products[calls].Id); calls++;});

I am using AutoMapper and have a definition of mapping engine as

private readonly IMappingEngine _mappingEngine;

I initialize it via constructor injection and use in the code as below

var product=//Get a single product
var productModel = _mappingEngine.Map<ProductModel>(product);

The above works perfectly. I now need to map a list of Product to list of ProductModel The following works in the controller action

var entities =//Get list of products
var model = entities.Select(e => _mappingEngine.Map<ProductModel>(e));

The above LINQ code uses foreach and converts each Product to a ProductModel Now I need to unit test the above code but unable to use Moq to mock the LINQ statement above

I've tried the following

var mapper = new Mock<IMappingEngine>();
mapper.Setup(m => m.Map<ProductModel>(product)).Returns(ProductModel);

The above mapper setup works for single object mapping. How can we setup the above using a list of products

So, I want to be able to setup a list of Product like this:

var productList=new List<Product>{new Product{Id=1,name="product 1"},
                                  new Product{Id=2,name="product 2"}};

and define a mocking that will return a list of ProductModel like this:

var productModelList=new List<ProductModel>{new ProductModel{Id=1,name="product 1"},
                                            new ProductModel{Id=2,name="product 2"}};  

when my test calls the controller (which uses the mock IMappingEngine to transform the list)

var model = entities.Select(e => _mappingEngine.Map<ProductModel>(e));

So, when writing Moq unit tests how can we setup the above so that _mappingEngine.Map that takes productList as input and returns productModelList?

解决方案

First up, do you really need to mock the mapping from Product to ProductModel, or would it be just as valid to create an instance of IMappingEngine and supply it to your controller, rather than a Mock.Object.

Something as simple adding the mapping in your test setup:

Mapper.CreateMap<Product, ProductModel>();

and clearing the mapping in your test teardown:

Mapper.Reset();

Would allow you to just supply Mapper.Engine to controllers constructor in your test. Obviously it depends on your approach to testing, but for something like AutoMapper which is well used and reliable, with little time overhead, this may well be a valid approach.

Assuming you do want to mock the mapping, you can use a callback to return a different item for each call, doing something like this:

// Create a list of the mapped values you're expecting
var productModels = new List<ProductModel> {
    new ProductModel { Id=11,name="eleven"},
    new ProductModel { Id=12,name="twelve"},
    new ProductModel { Id=13,name="thirteen"}
};

// Mock the IMappingEngine
var engine = new Mock<IMappingEngine>();

// Create a variable to count the calls
var calls=0;

// Mock ALL calls to map, where the destination is ProductModel
// and source is Product
engine.Setup(m => m.Map<ProductModel>(It.IsAny<Product>()))
      .Returns(()=>productModels[calls]) // Return next productModel
      .Callback(()=>calls++)   // Increment counter to point to next model

You should notice, that the mock Setup is mocking individual mappings, not mapping a list of Product to a list of ProductModel. That's because your call to entities.Select(e => _mappingEngine.Map<ProductModel>(e)) is looping through your list an item at a time, not asking the mapping engine (or your mock) to map the list in one go...

If you need to be more precise in your mocking, then it's also possible to extend the Callback to verify that the expected Product is being mapped. You probably won't want to do this every time, but it can be useful in some circumstances. So, you could do something like:

// Declare a list of expected products to map from
var products = new List<Product> { 
    new Product {Id=1, name="One"},
    new Product {Id=2, name="Two"},
    new Product {Id=3, name="Three"}
};


engine.Setup(m => m.Map<ProductModel>(It.IsAny<Product>()))
      .Returns(() => productModels[calls])
      .Callback<Product>(x => {Assert.AreEqual(x.Id, products[calls].Id); calls++;});

这篇关于如何使用AutoMapper模拟列表转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆