我想对采用FormCollection
且具有Find
方法的控制器进行单元测试。我已经成功实现了假DbContext
和FakeDbSet
(来自here),以便我可以在我的测试中使用假对象。
我想测试的具体方法如下:
//
// 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));
}
这是我使用OrderController
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);
}
然后测试就这样确认未经证实的订单正在被确认。
[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;
}
不幸的是我收到以下错误消息:
测试名称:TestSettleOrdersPost 结果消息:
System.NullReferenceException:未将对象引用设置为实例 一个对象。结果StackTrace:at Controllers.OrderController.SettleOrders(FormCollection c)in Controllers \ OrderController.cs:第121行
这可能与假的Find
方法有关,没有做它应该做的事情。我使用的find方法如下所示:
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;
}
}
我不知道如何改进此代码。看着它我相信它应该有效。由于我是一个为期7天的单位测试用户,因此这种信念并不值得。我希望有人可以帮助我。
答案 0 :(得分:2)
我认为您可以在通用Find
类中包含FakeDbSet<T>
方法实现。
例如,如果您的所有entites都具有映射到数据库中主键的属性Id
,则可以执行以下操作:
public class FakeDbSet<T>
{
....
public T Find(params object[] keyvalues)
{
var keyProperty = typeof(T).GetProperty(
"Id",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
var result = this.SingleOrDefault(obj =>
keyProperty.GetValue(obj).ToString() == keyValues.First().ToString());
return result;
}
}
如果您的密钥属性不是Id
,您可以尝试通过反射确定如何识别实体的密钥属性的某些算法(例如,如果Order
类具有OrderId
密钥属性,则可以尝试通过使用反射获取类名并将其与“Id”字符串连接来确定它。
答案 1 :(得分:1)
我遇到同样的问题:
public override TEntity Find(params object[] keyValues)
{
if (keyValues.Length == 0)
throw new ArgumentNullException("keyValues");
return this.SingleOrDefault(GenerateKeyFilter(keyValues));
}
private Expression<Func<TEntity, bool>> GenerateKeyFilter(object[] keyValues)
{
var conditions = new List<BinaryExpression>();
var objectParam = Expression.Parameter(typeof(TEntity));
var keyFields = !!!Helper.KeyFields<TEntity>();
if (keyFields.Count != keyValues.Length)
throw new KeyNotFoundException();
for (var c = 0; c < keyFields.Count; c++)
conditions.Add(Expression.MakeBinary(
ExpressionType.Equal,
Expression.MakeMemberAccess(objectParam, keyFields[c]),
Expression.Constant(keyValues[c], keyFields[c].PropertyType)
));
var result = conditions[0];
for (var n = 1; n < conditions.Count; n++)
result = Expression.And(result, conditions[n]);
return Expression.Lambda<Func<TEntity, bool>>(result, objectParam);
}
但是,这很有效,我需要重新配置模型并手动设置密钥,类似于使用 EntityTypeConfiguration 。