如何使用AutoMapper模拟列表转换 [英] How to Mock a list transformation using 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
?
首先,您真的是否需要模拟从Product
到ProductModel
的映射,或者仅仅是有效地创建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屋!