I want to unit test a controller that takes a FormCollection
and has the Find
method. I have successfully implemented a fake DbContext
and a FakeDbSet
(from here) so that I can use the fake objects in my tests.
The specific method I want to test looks like this:
//
// POST: /Order/SettleOrders
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SettleOrders(FormCollection c)
{
int i = 0;
if (ModelState.IsValid)
{
var ordersCollection = new List<Order>();
// Get values of form
var OrderIdArray = c.GetValues("item.OrderId");
var UnitPriceArray = c.GetValues("item.UnitPrice");
for (i = 0; i < OrderIdArray.Count(); i++)
{
// Find order in database and update the unitprice and isconfirmed status
Order order = db.Orders.Find(Convert.ToInt32(OrderIdArray[i]));
order.UnitPrice = Convert.ToDecimal(UnitPriceArray[i]);
order.IsConfirmed = true;
db.SetModified(order);
ordersCollection.Add(order);
}
db.SaveChanges();
}
// Return orders of this date to view
var currentDate = DateTime.Today;
var orders = db.Orders.Include(o => o.Product)
.Include(o => o.User)
.Where(o => o.Date == currentDate);
return View("Confirmation", orders.ToList().OrderBy(o => o.User.Name));
}
This is how I set up the test of my OrderController
using Moq
:
[TestInitialize]
public void OrderControllerTestInitialize()
{
// Arrange
var unconfirmedMemoryItems = new FakeOrderSet
{
// @TODO Tests/methods should ideally not be dependent on DateTime.Today...
new Order { OrderId = 1, UnitPrice = 1.00M, Quantity = 2, Date = DateTime.Today, IsConfirmed = false },
new Order { OrderId = 2, UnitPrice = 2.00M, Quantity = 1, Date = DateTime.Today, IsConfirmed = false }
};
// Create mock unit of work
var unconfirmedMockData = new Mock<ISeashellBrawlContext>();
unconfirmedMockData.Setup(m => m.Orders).Returns(confirmedMemoryItems);
// Setup controller
unconfirmedOrderController = new OrderController(confirmedMockData.Object);
}
Then the test goes likes this to confirm the unconfirmed orders are becoming confirmed.
[TestMethod]
public void TestSettleOrdersPost()
{
// Invoke
FormCollection form = CreatesettleOrdersPostFormCollection();
var viewResult = unconfirmedOrderController.SettleOrders(form) as ViewResult;
var ordersFromView = (IEnumerable<Order>)viewResult.Model;
// Assert
Assert.AreEqual(3, ordersFromView.ElementAt(0).Quantity,
"New item should be added to older one since it has same index and is of same date");
Assert.AreEqual(true, ordersFromView.ElementAt(0).IsConfirmed,
"The item should also be set to confirmed");
}
// Helper methods
private static FormCollection CreatesettleOrdersPostFormCollection()
{
FormCollection form = new FormCollection();
form.Add("item.OrderId", "1");
form.Add("item.UnitPrice", "2.00");
form.Add("item.OrderId", "2");
form.Add("item.UnitPrice", "3.00");
return form;
}
Unfortunately I get the following error message:
Test Name: TestSettleOrdersPost Result Message:
System.NullReferenceException: Object reference not set to an instance of an object. Result StackTrace: at Controllers.OrderController.SettleOrders(FormCollection c) in Controllers\OrderController.cs:line 121
This probably has to do with the fake Find
method not doing what it is supposed to do. The find method I use looks like this:
class FakeOrderSet : FakeDbSet<Order>
{
public override Order Find(params object[] keyValues)
{
var id = Convert.ToInt32(keyValues[0]);
Order o = null;
IQueryable<Order> keyQuery = this.AsQueryable<Order>();
foreach (var order in keyQuery)
{
if (order.OrderId == id)
{
o = order;
}
}
return o;
}
}
I have no idea how to improve this code. Looking at it I am convinced it should work. Since I am a 7-day noob to unit testing this conviction is of not much worth though. I hope someone can help me out.