我是一位经验丰富的程序员,但对 LINQ/Moq/Ninject/MVC/MS Test/etc 不熟悉,并且遇到了一个我无法弄清楚的问题。
我从 Pro ASP.NET MVC 2 Framework 书(但使用 .NET 4.5/MVC 4)构建了 SportsStore 示例。我得到了这个工作,现在我已经开始将它转换为与我们的真实数据库一起工作。此时的主要区别在于,我们不仅有一个 Product 类,还有一个 ProductSub 类。每个产品类由 1 个或多个 ProductSub 组成,我已经用 EntitySet 关联定义了它。为了让 CartController 知道要将哪个 ProductSub 添加到购物车中,我决定将 CartController.AddToCart 更改为使用 productSubId 而不是 productId。
当我运行网站并手动单击“添加产品”时,一切似乎都正常。但是,当我运行单元测试时,我得到一个 NullReferenceException,因为 cart.Lines[0] 为空。我不认为错误出现在 CartController 中,因为当我运行网页时这似乎有效,我尝试使用 FakeProductsRepository(修改为添加 ProductSubID)来排除 Moq 导致此问题(没有帮助,所以我不'认为错误与起订量无关)。
我发现 CartController 中的这一行在单元测试中返回 null 但在我运行网页时没有:
productsRepository.ProductSubs.FirstOrDefault(ps => ps.ProductSubID == productSubId);
因此,我尝试对 CartController 进行硬编码,以查看 LINQ to Product 是否可以正常工作,它确实做到了!我认为这意味着 productsRepository 有产品,但由于某种原因,产品没有 ProductSub。到目前为止我是对的吗?
我最好的猜测是单元测试中的这段代码有问题:
new Product { ProductID = 2, ProductSubs = new List<ProductSub> { new ProductSub { ProductSubID = 456} } }
但我不知道是什么。使用List有错吗?我尝试使用 EntitySet 代替,但它得到了同样的错误。
单元测试代码:
[TestMethod]
public void Can_Add_Product_To_Cart()
{
// Arrange: Give a repository with some products...
var mockProductsRepository = UnitTestHelpers.MockProductsRepository(
new Product { ProductID = 1, ProductSubs = new List<ProductSub> { new ProductSub { ProductSubID = 123 } } },
new Product { ProductID = 2, ProductSubs = new List<ProductSub> { new ProductSub { ProductSubID = 456 } } }
);
var cartController = new CartController(mockProductsRepository, null);
var cart = new Cart();
// Act: When a user adds a product to their cart...
cartController.AddToCart(cart, 456, null);
// Assert: Then the product is in their cart
Assert.AreEqual(1, cart.Lines.Count);
Assert.AreEqual(456, cart.Lines[0].ProductSub.ProductSubID);
}
购物车类:
public class Cart
{
private List<CartLine> lines = new List<CartLine>();
public IList<CartLine> Lines { get { return lines.AsReadOnly(); } }
public void AddItem(ProductSub productSub, int quantity)
{
var line = lines.FirstOrDefault(x => x.ProductSub.ProductSubID == productSub.ProductSubID);
if (line == null)
lines.Add(new CartLine { ProductSub = productSub, Quantity = quantity });
else
line.Quantity += quantity;
}
public decimal ComputeTotalValue()
{
return lines.Sum(l => (decimal)l.ProductSub.Price * l.Quantity);
}
public void Clear()
{
lines.Clear();
}
public void RemoveLine(ProductSub productSub)
{
lines.RemoveAll(l => l.ProductSub.ProductSubID == productSub.ProductSubID);
}
}
public class CartLine
{
public ProductSub ProductSub { get; set; }
public int Quantity { get; set; }
}
产品类别:
[Table]
public class Product
{
[HiddenInput(DisplayValue = false)]
[Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int ProductID { get; set; }
[Required(ErrorMessage = "Please enter a product name")]
[Column]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a description")]
[DataType(DataType.MultilineText)]
[Column(Name = "info")]
public string Description { get; set; }
public float LowestPrice
{
get { return (from product in ProductSubs select product.Price).Min(); }
}
private EntitySet<ProductSub> _ProductSubs = new EntitySet<ProductSub>();
[System.Data.Linq.Mapping.Association(Storage = "_ProductSubs", OtherKey = "ProductID")]
public ICollection<ProductSub> ProductSubs
{
get { return _ProductSubs; }
set { _ProductSubs.Assign(value); }
}
[Required(ErrorMessage = "Please specify a category")]
[Column]
public string Category { get; set; }
}
[Table]
public class ProductSub
{
[HiddenInput(DisplayValue = false)]
[Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int ProductSubID { get; set; }
[Column(Name = "products_id")]
private int ProductID;
private EntityRef<Product> _Product = new EntityRef<Product>();
[System.Data.Linq.Mapping.Association(Storage = "_Product", ThisKey = "ProductID")]
public Product Product
{
get { return _Product.Entity; }
set { _Product.Entity = value; }
}
[Column]
public string Name { get; set; }
[Required]
[Range(0.00, double.MaxValue, ErrorMessage = "Please enter a positive price")]
[Column]
public float Price { get; set; }
}
UnitTestHelpers 代码(应该没问题,因为我尝试了 FakeProductsRepository):
public static IProductsRepository MockProductsRepository(params Product[] products)
{
var mockProductsRepos = new Mock<IProductsRepository>();
mockProductsRepos.Setup(x => x.Products).Returns(products.AsQueryable());
return mockProductsRepos.Object;
}
CartController 代码(应该没问题,因为它可以在网页上运行):
public RedirectToRouteResult AddToCart(Cart cart, int productSubId, string returnUrl)
{
//Product product = productsRepository.Products.FirstOrDefault(p => p.ProductID == 2);
//cart.AddItem(product.ProductSubs.FirstOrDefault(), 1);
ProductSub productSub = productsRepository.ProductSubs.FirstOrDefault(ps => ps.ProductSubID == productSubId);
cart.AddItem(productSub, 1);
return RedirectToAction("Index", new { returnUrl });
}
FakeProductsRepository 的代码:
public class FakeProductsRepository : IProductsRepository
{
private static IQueryable<Product> fakeProducts = new List<Product> {
new Product { Name = "Football", ProductSubs = new List<ProductSub> { new ProductSub { ProductSubID = 123, Price = 25 } } },
new Product { Name = "Surf board", ProductSubs = new List<ProductSub> { new ProductSub { ProductSubID = 456, Price = 179 } } },
new Product { Name = "Running shoes", ProductSubs = new List<ProductSub> { new ProductSub { ProductSubID = 789, Price = 95 } } }
}.AsQueryable();
public FakeProductsRepository(params Product[] prods)
{
fakeProducts = new List<Product>(prods).AsQueryable();
}
public IQueryable<Product> Products
{
get { return fakeProducts; }
}
public IQueryable<ProductSub> ProductSubs
{
get { return fakeProducts.SelectMany(ps => ps.ProductSubs); }
}
public void SaveProduct(Product product)
{
throw new NotImplementedException();
}
public void DeleteProduct(Product product)
{
throw new NotImplementedException();
}
}
如果您需要任何其他信息,请告诉我。