私は経験豊富なプログラマーですが、LINQ / Moq / Ninject / MVC / MS Test / etcに不慣れで、理解できない問題に遭遇しました。
私はProASP.NETMVC 2 FrameworkブックからSportsStoreサンプルを作成しました(ただし、.NET 4.5 / MVC 4を使用)。私はそれを機能させ、今では実際のデータベースで機能するように変換し始めました。この時点での主な違いは、Productクラスだけでなく、ProductSubクラスもあることです。各Productクラスは1つ以上のProductSubで構成されており、これをEntitySetアソシエーションで定義しました。CartControllerにカートに追加するProductSubを認識させるために、CartController.AddToCartを変更してproductIdではなくproductSubIdを取得することにしました。
Webサイトを実行し、手動で[製品の追加]をクリックすると、すべてが正常に機能しているようです。ただし、単体テストを実行すると、cart.Lines [0]がnullであるため、NullReferenceExceptionが発生します。CartControllerにエラーがあるとは思わないので、Webページを実行すると機能するようです。また、FakeProductsRepository(ProductSubIDを追加するように変更)を使用して、これを引き起こすMoqを除外しようとしました(これは役に立たなかったので、私はしませんエラーがMoqと関係があるとは思わない)。
CartControllerのこの行は、単体テストではnullを返しますが、Webページを実行するとnullを返すことがわかりました。
productsRepository.ProductSubs.FirstOrDefault(ps => ps.ProductSubID == productSubId);
そこで、CartControllerをハードコーディングして、代わりにLINQ totheProductが機能するかどうかを確認しました。つまり、productsRepositoryにはProductがありますが、何らかの理由でProductにはProductSubがありません。私は今のところ正しいですか?
私の最善の推測は、単体テストのこのコードに何か問題があることです。
new Product { ProductID = 2, ProductSubs = new List<ProductSub> { new ProductSub { ProductSubID = 456} } }
しかし、私は何を理解することはできません。リストを使用するのは間違っていますか?代わりに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コード(Webページで機能するので問題ありません):
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();
}
}
その他の情報が必要な場合はお知らせください。