Automatic unit testing
I am having trouble getting the auto mapper to work in my unit test. I am implementing a mapping engine and this works great in code but not in test. Here is my test and setup. I am using Moq to mock the mapping engine.
private static IDbContext Context { get; set; }
private static Mock<IMappingEngine> Mapper { get; set; }
private static Guid MenuId { get; set; }
private static Guid menuItem1Id { get; set; }
private static Guid menuItem2Id { get; set; }
private static Guid menuItem3Id { get; set; }
[ClassInitialize]
public static void SetUp(TestContext context)
{
MenuId = Guid.NewGuid();
Context = new TestDbContext();
menuItem1Id = Guid.NewGuid();
menuItem2Id = Guid.NewGuid();
menuItem3Id = Guid.NewGuid();
var contentPage1 = new ContentPage { Id = Guid.NewGuid(), ActionName = "ActionName1", ControllerName = "ControllerName1", MenuItemId = menuItem1Id };
var contentPage2 = new ContentPage { Id = Guid.NewGuid(), ActionName = "ActionName2", ControllerName = "ControllerName2", MenuItemId = menuItem2Id };
var contentPage3 = new ContentPage { Id = Guid.NewGuid(), ActionName = "ActionName3", ControllerName = "ControllerName3", MenuItemId = menuItem3Id };
var menuItem1 = new MenuItem { Id = menuItem1Id, MenuId = MenuId, DisplayName = "MenuItem1", ExternalUrl = null, Target = Target._self, Active = true, ContentPage = contentPage1 };
var menuItem2 = new MenuItem { Id = menuItem1Id, MenuId = MenuId, DisplayName = "MenuItem2", ExternalUrl = null, Target = Target._self, Active = true, ContentPage = contentPage2 };
var menuItem3 = new MenuItem { Id = menuItem1Id, MenuId = MenuId, DisplayName = "MenuItem3", ExternalUrl = null, Target = Target._self, Active = true, ContentPage = contentPage3 };
var menu = new Models.Menu { Id = MenuId, Name = "TestMenu", SiteId = Guid.NewGuid(), MenuItems = new List<MenuItem> { menuItem1, menuItem2, menuItem3 } };
Context.Menus.Add(menu);
Context.MenuItems.Add(menuItem1);
Context.MenuItems.Add(menuItem2);
Context.MenuItems.Add(menuItem3);
var menuItemQueryResult = new List<MenuItemQueryResult>
{
new MenuItemQueryResult { Id = menuItem1Id, DisplayName = "MenuItem1", ExternalUrl = null, Target = Target._self, Active = true, ActionName = "ActionName1", ControllerName = "ControllerName1" },
new MenuItemQueryResult { Id = menuItem1Id, DisplayName = "MenuItem2", ExternalUrl = null, Target = Target._self, Active = true, ActionName = "ActionName2", ControllerName = "ControllerName2" },
new MenuItemQueryResult { Id = menuItem1Id, DisplayName = "MenuItem3", ExternalUrl = null, Target = Target._self, Active = true, ActionName = "ActionName3", ControllerName = "ControllerName3" }
};
Mapper = new Mock<IMappingEngine>();
Mapper.Setup(m => m.Map<IEnumerable<MenuItem>, IEnumerable<MenuItemQueryResult>>(It.IsAny<IEnumerable<MenuItem>>()))
.Returns(menuItemQueryResult);
}
[TestMethod]
public void Retrieve_RequestMenu_QueryResultReturned()
{
var handler = new MenuQueryHandler(Context, Mapper.Object);
var query = new MenuQuery("TestMenu");
var result = handler.Retrieve(query);
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(MenuQueryResult));
var item = result.FirstOrDefault(r => r.Id == menuItem1Id);
Assert.IsNotNull(item);
}
And here is what I am testing:
public class MenuQueryHandler : IQueryHandler<MenuQuery, MenuQueryResult>
{
private IDbContext Context { get; set; }
private IMappingEngine Mapper { get; set; }
public MenuQueryHandler(IDbContext context, IMappingEngine mapper)
{
Context = context;
Mapper = mapper;
}
public MenuQueryResult Retrieve(MenuQuery query)
{
Ensure.Argument.Is(query != null);
Ensure.Argument.IsNot(query.MenuName == string.Empty);
// Create the view model query result
var result = new List<MenuItemQueryResult>();
// Pull the required item from the cont.ext
var menuItems = Context.MenuItems.Include(m => m.ContentPage).ToList();
Mapper.Map(menuItems, result);
return new MenuQueryResult(result);
}
}
The test runs, but the match never happens.
source to share
There are several problems:
-
You are not mocking the method you actually call it. The method you are testing calls this method:
TDestination Map<TSource, TDestination>(TSource source, TDestination destination);
This overload
Map
takes an existing objectdestination
and maps to it.In your test, you mock an overload
Map
that returns a new instanceTDestination
:TDestination Map<TSource, TDestination(TSource source);
Note that the one you are kidding takes one parameter, and the one you actually call takes two.
-
Your method
Setup
makes a fake match betweenIEnumerable<MenuItem>
andIEnumerable<MenuItemQueryResult>
. In your test, you are actually callingMap
withList<MenuItem>
andList<MenuItemQueryResult>
.In real use, AutoMapper will handle the mapping
List
toList
for your use by mappingIEnumerable
. However, if you mock a method, you are not actually calling the method with the exact parameters you specified. Therefore, you will have to change the callSetup
and change the fake mapping.
So, to get around these problems, you can do one of two things:
-
Change the overload usage method
Map
that returns a new instance.It doesn't look like you need to use an overload that takes two parameters, so you can customize the method:
var result = Mapper.Map<List<MenuItemQueryResult>(menuItems);
And then in your test:
Mapper.Setup(m => m.Map<List<MenuItem>, List<MenuItemQueryResult>>(It.IsAny<List<MenuItem>>()) Mapper .Setup(m => m.Map<List<MenuItem>, List<MenuItemQueryResult>>(It.IsAny<List<MenuItem>>())) .Returns(menuItemQueryResult);
-
Modify the test to poke fun at correct overload
Map
.It's a little less intuitive, but possible. You will need to provide a fake method implementation
Map
:Mapper .Setup(m => m.Map<List<MenuItem>, List<MenuItemQueryResult>>(It.IsAny<List<MenuItem>>(), It.IsAny<List<MenuItem>>())) .Callback((List<MenuItem> menuItems, List<MenuItemQueryResult> queryResults) => { queryResults.AddRange(menuItemQueryResult); });
source to share
Your mock mapper is set up to return a menuItemQueryResult, but you don't take the result of the Mapper.Map function when you implement it. In my opinion you should use your cartographer like this:
result = Mapper.Map(menuItems);
Edit:
If you are using Automapper and configured correctly for types:
result = Mapper.Map<IEnumerable<MenuItem>, List<MenuItemQueryResult>>(menuItems);
source to share