Error Trying to pass a IEnumerable list with Linq and Mvc Web API - asp.net-mvc

I'm trying to return a result from a list with a Linq method but i get an error saying that I "implictly can't convert my class to generic list. I'm using Get... method.
Would very much appreciate if someone could help me out with this.
This is my Tamagotchi class:
public class Tamagotchi
{
public string Name { get; set; }
public DateTime Born { get; set; }
public int Health { get; set; }
}
This is the API:
public class SomeController : ApiController
{
List<Tamagotchi> Gotchis = new List<Tamagotchi>
{
new Tamagotchi { Born = DateTime.Now.AddYears(-5), Health = 30, Name = "XTP" },
new Tamagotchi { Born = DateTime.Now.AddYears(-4), Health = 49, Name = "ZQX" },
new Tamagotchi { Born = DateTime.Now.AddYears(-3), Health = 15, Name = "VBR" },
new Tamagotchi { Born = DateTime.Now.AddYears(-2), Health = 87, Name = "BNQP" },
new Tamagotchi { Born = DateTime.Now.AddYears(-1), Health = 62, Name = "VLW" },
};
public IEnumerable<Tamagotchi> Get2()
{
var _result = Gotchis.SingleOrDefault(tama => tama.Name == "VLW");
return _result;
}
}
Thank you!
/Chris

SingleOrDefault will return a single Tamagutchi, so the result is not an IEnumerable at all. Perhaps you mean
public Tamagotchi Get2()
{
var _result = Gotchis.SingleOrDefault(tama => tama.Name == "VLW");
return _result;
}
or
public IEnumerable<Tamagotchi> Get2()
{
var _result = Gotchis.Where(tama => tama.Name == "VLW");
return _result;
}

Related

I am new to unit testing and i'd like to know why it is not working

I am new to MVC and to unit testing so I have been following guides etc.
At the moment I am looking at unit testing. I have a test which as far as I can see should work, but unfortunately does not.
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using WorkingWithVisualStudio.Controllers.Home;
using WorkingWithVisualStudio.Models;
using Xunit;
namespace WorkingWithVisualStudio.Tests
{
public class HomeControllerTests
{
class ModelCompleteFakeRepository : IRepository
{
public IEnumerable<Product> Products { get; } = new Product[] {
new Product { Name = "P1", Price = 275M },
new Product { Name = "P2", Price = 48.95M },
new Product { Name = "P3", Price = 19.50M },
new Product { Name = "P3", Price = 34.95M }};
public void AddProduct(Product p)
{
// do nothing - not required for test
}
}
[Fact]
public void IndexActionModelIsComplete()
{
// Arrange
var controller = new HomeController();
controller.Repository = new ModelCompleteFakeRepository();
// Act
var model = (controller.Index() as ViewResult)?.ViewData.Model
as IEnumerable<Product>;
// Assert
Assert.Equal(controller.Repository.Products, model,
Comparer.Get<Product>((p1, p2) => p1.Name == p2.Name
&& p1.Price == p2.Price));
}
class ModelCompleteFakeRepositoryPricesUnder50 : IRepository
{
public IEnumerable<Product> Products { get; } = new Product[] {
new Product { Name = "P1", Price = 5M },
new Product { Name = "P2", Price = 48.95M },
new Product { Name = "P3", Price = 19.50M },
new Product { Name = "P3", Price = 34.95M }};
public void AddProduct(Product p)
{
// do nothing - not required for test
}
}
[Fact]
public void IndexActionModelIsCompletePricesUnder50()
{
// Arrange
var controller = new HomeController();
controller.Repository = new ModelCompleteFakeRepositoryPricesUnder50();
// Act
var model = (controller.Index() as ViewResult)?.ViewData.Model
as IEnumerable<Product>;
// Assert
Assert.Equal(controller.Repository.Products, model,
Comparer.Get<Product>((p1, p2) => p1.Name == p2.Name
&& p1.Price == p2.Price));
}
}
}
When I run the IndexActionModelIsCompletePricesUnder50()
I get the following:
Message: Assert.Equal() Failure
Expected: Product[] [Product { Name = "P1", Price = 5 }, Product { Name = "P2", Price = 48.95 }, Product { Name = "P3", Price = 19.50 }, Product { Name = "P3", Price = 34.95 }]
Actual: ValueCollection<String, Product> [Product { Name = "Kayak", Price = 275 }, Product { Name = "Lifejacket", Price = 48.95 }, Product { Name = "Soccer ball", Price = 19.50 }, Product { Name = "Corner flag", Price = 34.95 }]
My model is as follows:
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
My repository:
public class SimpleRepository : IRepository
{
private static SimpleRepository sharedRepository = new SimpleRepository();
private Dictionary<string, Product> products = new Dictionary<string, Product>();
public static SimpleRepository SharedRepository => sharedRepository;
public SimpleRepository()
{
var initialItems = new[]
{
new Product {Name = "Kayak", Price = 275M},
new Product { Name = "Lifejacket", Price = 48.95M },
new Product { Name = "Soccer ball", Price = 19.50M },
new Product { Name = "Corner flag", Price = 34.95M }
};
foreach(var p in initialItems)
{
AddProduct(p);
}
//products.Add("Error", null);
}
public IEnumerable<Product> Products => products.Values;
public void AddProduct(Product p) => products.Add(p.Name, p);
}
My repository interface
public interface IRepository
{
IEnumerable<Product> Products { get; }
void AddProduct(Product p);
}
My comparer:
public class Comparer
{
public static Comparer<U> Get<U>(Func<U, U, bool> func)
{
return new Comparer<U>(func);
}
}
public class Comparer<T> : Comparer, IEqualityComparer<T>
{
private Func<T, T, bool> comparisonFunction;
public Comparer(Func<T, T, bool> func)
{
comparisonFunction = func;
}
public bool Equals(T x, T y)
{
return comparisonFunction(x, y);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
}
my controller:
public class HomeController : Controller
{
public IRepository Repository = SimpleRepository.SharedRepository;
public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);
[HttpGet]
public IActionResult AddProduct() => View(new Product());
[HttpPost]
public IActionResult AddProduct(Product p)
{
Repository.AddProduct(p);
return RedirectToAction("Index");
}
}
I am sorry if this seems like a stupid question but I have only just begun to look into unit testing. If someone could explain to me what the issue is I would definitely appreciate it. Thank you very much to those who take the time to lend a hand.
I would first suggest you refactor the controller to follow a more SOLID approach by using Explicit Dependency Principle
Methods and classes should explicitly require (typically through method parameters or constructor parameters) any collaborating objects they need in order to function correctly.
So the controller would end up looking like this
public class HomeController : Controller {
private readonly IRepository repository;
public HomeController(IRepository repository) {
this.repository = repository;
}
public IActionResult Index() => View(repository.Products.ToList());
[HttpGet]
public IActionResult AddProduct() => View(new Product());
[HttpPost]
public IActionResult AddProduct(Product p) {
repository.AddProduct(p);
return RedirectToAction("Index");
}
}
So as to avoid the mistake initially made with accessing the share repository during an isolated unit test, which caused your assertions to fail.
Try to avoid tightly coupling your classes to static or shared dependencies. It would be safer to inject the abstraction of that dependency.
A simplified version of the test can now be clearly exercised as follows.
class ModelCompleteFakeRepository : IRepository {
public IEnumerable<Product> Products { get; } = new Product[] {
new Product { Name = "P1", Price = 275M },
new Product { Name = "P2", Price = 48.95M },
new Product { Name = "P3", Price = 19.50M },
new Product { Name = "P3", Price = 34.95M }
};
public void AddProduct(Product p) {
// do nothing - not required for test
}
}
[Fact]
public void IndexActionModelIsComplete() {
// Arrange
var repository = new ModelCompleteFakeRepository();
var controller = new HomeController(repository);
var expected = repository.Products;
// Act
var actual = (controller.Index() as ViewResult)?.ViewData.Model as IEnumerable<Product>;
// Assert
Assert.IsNotNull(actual);
Assert.Equal(expected, actual);
}
Because in your Index method you're referring to the SimpleRepository, not you Repository member.
Replace
public IActionResult Index() => View(SimpleRepository.SharedRepository.Products);
with
public IActionResult Index() => View(Repository.Products);
I should add as well that you might want to have a look at the structure of your code, and inject the repository in the constructor instead. And also, the two different tests you have test the same thing, so only one of them is necessary.
Edit: My answers solves your current issue, while #Nkosi answer shows how you should do this properly.

generic ralationship in neo4j c#

I need to establish a relationship between two different node type like this:
public class Fallow<T,U>: Relationship,
IRelationshipAllowingSourceNode<T>,
IRelationshipAllowingTargetNode<U>
{
public Fallow(NodeReference targetNode)
: base(targetNode)
{
}
public const string TypeKey = "FALLOW";
public DateTime relationDate { get; set; }
public override string RelationshipTypeKey
{
get { return TypeKey; }
}
}
I have an error:
Error 1 'Biber10.Neo4j.Fallow<T,U>' cannot implement both 'Neo4jClient.IRelationshipAllowingParticipantNode<T>' and 'Neo4jClient.IRelationshipAllowingParticipantNode<U>' because they may unify for some type parameter substitutions C:\Users\turgut\Documents\Visual Studio 2013\Projects\Biber10\Biber10.Neo4j\Fallow.cs 10 18 Biber10.Neo4j
How do I fix it?.
Thanks.
We've moved away from the use of Relationship like this, the best example of the thing you're trying to do would be something like this:
public class Fallow
{
public const string TypeKey = "FALLOW";
public DateTime RelationDate { get; set; }
}
Used like so:
//Just using this to create a demo node
public class GeneralNode
{
public string AValue { get; set; }
}
var gc = new GraphClient(new Uri("http://localhost.:7474/db/data/"));
gc.Connect();
//Create
var node1 = new GeneralNode { AValue = "val1"};
var node2 = new GeneralNode { AValue = "val2" };
var fallow = new Fallow { RelationDate = new DateTime(2016, 1, 1)};
gc.Cypher
.Create($"(n:Value {{node1Param}})-[:{Fallow.TypeKey} {{fallowParam}}]->(n1:Value {{node2Param}})")
.WithParams(new
{
node1Param = node1,
node2Param = node2,
fallowParam = fallow
})
.ExecuteWithoutResults();
//Get
var query = gc.Cypher
.Match($"(n:Value)-[r:{Fallow.TypeKey}]->(n1:Value)")
.Return(r => r.As<Fallow>());
var results = query.Results;
foreach (var result in results)
{
Console.WriteLine("Fallow: " + result.RelationDate);
}

How to mock data for HttpContext NUnit

I have written a test class for my controller class using NUnit and Moq framework.My classes are following
public class ClientTypeController : BaseController
{
IClientTypeService clientTypeService;
IClientTypeAudService clientTypeAudService;
IClientTypeHisService clientTypeHisService;
IUserAccountService userAccountService;
System.Web.HttpResponseBase _responceBase;
public ClientTypeController()
{
}
public ClientTypeController(IClientTypeService _IClientTypeService, IClientTypeAudService _IClientTypeAudService,
IClientTypeHisService _IClientTypeHisService,IUserAccountService
_IUserAccountService)
{
clientTypeService = _IClientTypeService;
clientTypeAudService = _IClientTypeAudService;
clientTypeHisService = _IClientTypeHisService;
userAccountService = _IUserAccountService;
}
public ClientTypeController(IClientTypeAudService _clientTypeAudService,
System.Web.HttpResponseBase responceBas)
{
clientTypeAudService = _clientTypeAudService;
this._responceBase = responceBas;
}
......
.....
And my test class for the above given controller class,
public class ClientTypeControllerTest
{
private Mock<IClientTypeService> _clientTypeServiceMock;
private Mock<IClientTypeAudService> _clientTypeAudServiceMock;
private Mock<IClientTypeHisService> _clientTypeHisServiceMock;
private Mock<HttpSessionStateBase> _sessionMock;
private Mock<HttpResponseBase> _httpresponceMock;
private Mock<IUserAccountService> _userAccountService;
ClientTypeController objClientTypeController;
protected Users LoggedInUser { get; set; }
List<ClientTypeAud> lists;
[SetUp]
public void Initialize()
{
//System.Diagnostics.Debugger.Launch();
_clientTypeServiceMock = new Mock<IClientTypeService>();
_clientTypeAudServiceMock = new Mock<IClientTypeAudService>();
_clientTypeHisServiceMock = new Mock<IClientTypeHisService>();
_userAccountService = new Mock<IUserAccountService>();
_sessionMock = new Mock<HttpSessionStateBase>();
_httpresponceMock = new Mock<HttpResponseBase>();
var ctrlContext = new Mock<ControllerContext>();
AutoMapperConfiguration.Configure();
_sessionMock.SetupGet(s => s["LOGGED_IN_USER"]).Returns(users);
ctrlContext.Setup(p => p.HttpContext.Session).Returns(_sessionMock.Object);
}
[Test]
public void Show_AllClientTypeRecords_InGridView_UnitTest()
{
lists = new List<ClientTypeAud>() {
new ClientTypeAud() { Id = 1, CTypeName = "INR", CompanyId = 1, Active = "Y" },
new ClientTypeAud() { Id = 1, CTypeName = "ABC", CompanyId = 1, Active = "Y" },
new ClientTypeAud() { Id = 1, CTypeName = "AVM", CompanyId = 1, Active = "Y" }
};
_clientTypeAudServiceMock.Setup(x => x.All()).Returns(lists.AsQueryable());
objClientTypeController = new ClientTypeController(_clientTypeServiceMock.Object,
_clientTypeAudServiceMock.Object, _clientTypeHisServiceMock.Object,
_userAccountService.Object);
var result = objClientTypeController.GridData("", "asc", 1, 1) as JsonResult;
Assert.IsNotNull(result.Data);
}
}
And my BaseController class is ,
public class BaseController : Controller
{
public SessionProvider SessionProvider;
protected Users LoggedInUser { get; set; }
public string actionName { get; set; }
protected string controllerName { get; set; }
protected string area { get; set; }
public BaseController()
{
actionName =
System.Web.HttpContext.Current.Request.RequestContext.RouteData.GetRequiredString("action");
controllerName =
System.Web.HttpContext.Current.Request.RequestContext.RouteData.
GetRequiredString("controller");
SessionProvider = new SessionProvider(Session);
LoginMethod();
ViewBag.Menu = BuildMenu();
}
......
......
When I run my test class using NUnit then it shows Nullreferenceexception was unhandled by user code on
actionName = System.Web.HttpContext.Current.Request.RequestContext.
RouteData.GetRequiredString("action");
This is in my BaseControllerClass. So I don't know how to Moq the data for HttpContext.Current.Request.RequestContext.so please Can anyone please help to find the solution
You should create wrapper utility for handling the HttpContext.
This wrapper should implement an interface and in your UT you should mock that call (you can use Rhino Mocks).

uCommerce - add dynamic property to order line

I have hit a problem building a uCommerce site based on top of the demo razor store available http://thesitedoctor.co.uk/portfolio/avenue-clothingcom/
The demo uses servicestack and the ucommerceapi for its basket functions.
I am trying to add a dynamic property to the basket (on an order line) at the point where the user clicks buy. I traced through the productpage.js file and amended the code to add a new property ('message'):
function (data) {
var variant = data.Variant;
$.uCommerce.addToBasket(
{
sku: variant.Sku,
variantSku: variant.VariantSku,
quantity: qty,
message: $('#personalisedMessage').val()
},
function () {
updateCartTotals(addToCartButton);
}
);
});
using firebug, i checked the data that is being posted
addToExistingLine: true
message: "this is a message"
quantity:"1"
sku: "Product (options: none)"
variantSku:""
Posting this does not cause an error, but I cannot tell if it has worked either - I cannot find it in the database, assuming that it would be stored in OrderProperty table. In this scenario, I am 'buying' a product with no variations.
Any help is greatly appreciated with this.
Out of the box you can't add order/line item properties via the API like that. The API payload that you've added to is specified although valid JSON won't get interpreted/used by the API.
Instead what you'll need to do is add your own method to the API. To do this you'll need to implement a service from IUCommerceApiService and then you can do what you need. I've created an example (untested) below and will get it added to the demo store as I think it's a useful bit of functionality to have.
public class AddOrderLineProperty
{
public int? OrderLineId { get; set; }
public string Sku { get; set; }
public string VariantSku { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class AddOrderLinePropertyResponse : IHasResponseStatus
{
public AddOrderLinePropertyResponse() { }
public AddOrderLinePropertyResponse(UCommerce.EntitiesV2.OrderLine line)
{
if (line == null)
{
UpdatedLine = new LineItem();
return;
}
var currency = SiteContext.Current.CatalogContext.CurrentCatalog.PriceGroup.Currency;
var lineTotal = new Money(line.Total.Value, currency);
UpdatedLine = new LineItem()
{
OrderLineId = line.OrderLineId,
Quantity = line.Quantity,
Sku = line.Sku,
VariantSku = line.VariantSku,
Price = line.Price,
ProductName = line.ProductName,
Total = line.Total,
FormattedTotal = lineTotal.ToString(),
UnitDiscount = line.UnitDiscount,
VAT = line.VAT,
VATRate = line.VATRate
};
}
public ResponseStatus ResponseStatus { get; set; }
public LineItem UpdatedLine { get; set; }
}
public class AddOrderLinePropertyService : ServiceBase<AddOrderLineProperty>, IUCommerceApiService
{
protected override object Run(AddOrderLineProperty request)
{
var orderLineId = request.OrderLineId;
var sku = request.Sku;
var variantSku = request.VariantSku;
var orderLine = findOrderLine(orderLineId, sku, variantSku);
addPropertyToOrderLine(orderLine, request.Key, request.Value);
TransactionLibrary.ExecuteBasketPipeline();
var newLine = findOrderLine(orderLineId, sku, variantSku);
return new AddOrderLinePropertyResponse(newLine);
}
private void addPropertyToOrderLine(OrderLine orderLine, string key, string value)
{
if (orderLine == null)
return;
orderLine[key] = value;
orderLine.Save();
}
private static OrderLine findOrderLine(int? orderLineId, string sku, string variantSku)
{
return orderLineId.HasValue
? getOrderLineByOrderLineId(orderLineId)
: getOrderLineBySku(sku, variantSku);
}
private static OrderLine getOrderLineBySku(string sku, string variantSku)
{
return String.IsNullOrWhiteSpace(variantSku)
? getOrderLines().FirstOrDefault(l => (l.Sku == sku))
: getOrderLines().FirstOrDefault(l => (l.Sku == sku && l.VariantSku == variantSku));
}
private static OrderLine getOrderLineByOrderLineId(int? orderLineId)
{
return getOrderLines().FirstOrDefault(l => l.OrderLineId == orderLineId);
}
private static ICollection<OrderLine> getOrderLines()
{
return TransactionLibrary.GetBasket().PurchaseOrder.OrderLines;
}
}
You'll need to add the new method to uCommerce.jQuery.js as well something like this:
addOrderLineProperty: function (options, onSuccess, onError) {
var defaults = {
orderLineId: 0
};
var extendedOptions = $.extend(defaults, options);
callServiceStack({ AddOrderLineProperty: extendedOptions }, onSuccess, onError);
}
Let me know if you have any issues using it.
Tim

Is my mocking for the EntitySet wrong?

I'm an experienced programmer, but new to LINQ/Moq/Ninject/MVC/MS Test/etc and have run into an issue I haven't been able to figure out.
I have built the SportsStore sample from the Pro ASP.NET MVC 2 Framework book (but with .NET 4.5/MVC 4). I got that working and now I've begun to convert it to work with our real database. The main difference at this point is that we do not only have a Product class, but also a ProductSub class. Each Product class consists of 1 or more ProductSub's and I have defined this with an EntitySet Association. To make the CartController to know which ProductSub to add to the Cart I decided to change CartController.AddToCart to take a productSubId instead of a productId.
Everything seems to work fine when I run the website and manually click "add product". However, when I run my unit tests I get a NullReferenceException because cart.Lines[0] is null. I don't think the error is in CartController since that seems to work when I run the webpage, and I tried to use the FakeProductsRepository (modified to add ProductSubID's) to rule out Moq causing this (which didn't help, so I don't think the error has anything to do with Moq).
I've figured out that this line in CartController returns null in the unit test but not when I run the webpage:
productsRepository.ProductSubs.FirstOrDefault(ps => ps.ProductSubID == productSubId);
So I tried to hard code the CartController to see if LINQ to the Product instead would work, which it did! I think that means that the productsRepository have Product's, but that for some reason the Product's doesn't have a ProductSub's. I'm I right so far?
My best guess is that there's something wrong with this code in the unit test:
new Product { ProductID = 2, ProductSubs = new List<ProductSub> { new ProductSub { ProductSubID = 456} } }
But I can't figure out what. Is it wrong to use List? I tried using EntitySet instead but it made got the same error.
Unit test code:
[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);
}
Cart class:
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; }
}
Product class:
[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 code (which should be fine since I tried the 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 code (which should be fine since it works on the webpage):
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 });
}
Code for 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();
}
}
Please let me know if you need any other information.
Even though you have provided a lot of code some necessary information is missing so I'm assuming that IProductsRepository.ProductSubs returns IQueryable<ProductSub>. The MockProductsRepository method creates a mock for IProductsRepository but does not do any setup for IProductsRepository.ProductSubs. The mocking framework will most likely return an empty IQueryable<ProductSub>.
In the AddToCart you try to find the ProductSub using productsRepository.ProductSubs.FirstOrDefault. Because the mock returns an empty collection FirstOrDefault will return null thus you call cart.AddItem(null, 1) which explains why cart.Lines[0] is null.
Before fixing the mock you could consider doing parameter validation, e.g.
public void AddItem(ProductSub productSub, int quantity)
{
if (productSub == null)
throw new ArgumentNullException("productSub");
if (quantity < 1)
throw new ArgumentOutOfRangeException("quantity");
Then when you rerun your test it will be much clearer where your problem is.
Next thing will then be to create a setup for IProductsRepository.ProductSubs in MockProductsRepository:
mockProductsRepos
.Setup(x => x.ProductSubs)
.Returns(products.SelectMany(p => p.ProductSubs).AsQueryable());
This simply creates a collection of all the ProductSub objects from the Product objects provided to MockProductsRepository. You can of course modify this as you see fit.
I figured out the solution thanks to Martin Liversage. The mock WAS wrong, but I didn't figure it out because my FakeProductsRepository was ALSO wrong. Due to the dependency between Products and ProductSubs I don't think his suggested change to the mock would work though (but please correct me if I'm wrong).
The issue in FakeProductsRepository was that the constructor overwrote the initial fakeProducts collection with an empty collection. Once I changed that to only overwrite the initial collection if a new collection was supplied as parameter the unit tests worked using the FakeProductsRepository.
public FakeProductsRepository(params Product[] products)
{
if (products != null)
fakeProducts = new List<Product>(products).AsQueryable();
}
Thus there was an issue with the mock since that still didn't work. To solve it all I needed to do was to remove the ProductSubs function from IProductsRepository (which I had intended as a shortcut, but which I realized messed up the mocking). Once I did that and accessed the ProductSubs through the Products in CartController everything worked again.
public RedirectToRouteResult AddToCart(Cart cart, int productSubId, string returnUrl)
{
ProductSub productSub = productsRepository.Products.SelectMany(p => p.ProductSubs).FirstOrDefault(ps => ps.ProductSubID == productSubId);
cart.AddItem(productSub, 1);
return RedirectToAction("Index", new { returnUrl });
}
That was all I needed, but to simplify the test code I also decided to use pure ProductSub objects where that was enough instead of accessing them through a Product. Where I needed the whole Product (ie when the IProductsRepository was involved I used this code which I think is cleaner then creating the whole object on one line (ie with new List etc):
var ps1 = new ProductSub { ProductSubID = 11 };
var p1 = new Product();
p1.ProductSubs.Add(ps1);

Resources