I am new to unit testing and i'd like to know why it is not working - asp.net-mvc

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.

Related

How to create DbModificationClause with CASE WHEN .... THEN

all
I was create IDbCommandTreeInterceptor and got the problem: EF provider wrong sql generation. As a result, I want to get this SQL
UPDATE [dbo].[Devices] SET [DeletedDate] = CASE
WHEN [DeletedDate] IS NULL THEN GETUTCDATE()
ELSE [DeletedDate] END
Code for testing. Interseptor class for fake delettion.
public class SoftDeleteInterseptor : IDbCommandTreeInterceptor
{
private const string DELETED_DATE_COLUMN = "DeletedDate";
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace)
{
return;
}
var deleteCommand = interceptionContext.OriginalResult as DbDeleteCommandTree;
if (deleteCommand != null)
{
interceptionContext.Result = HandleDeleteCommand(deleteCommand);
return;
}
}
private DbCommandTree HandleDeleteCommand(DbDeleteCommandTree deleteCommand)
{
if (!IsPropertyExists(deleteCommand, DELETED_DATE_COLUMN))
{
return deleteCommand;
}
var deletedProperty = DbExpressionBuilder.Property(
DbExpressionBuilder.Variable(deleteCommand.Target.VariableType, deleteCommand.Target.VariableName),
DELETED_DATE_COLUMN
);
var caseValue = DbExpressionBuilder.Case(
new DbExpression[] { deletedProperty.IsNull() },
new DbExpression[] { EdmFunctions.CurrentUtcDateTime() },
deletedProperty);
var setClauses = new List<DbModificationClause> { DbExpressionBuilder.SetClause(deletedProperty, caseValue) };
return new DbUpdateCommandTree(
deleteCommand.MetadataWorkspace,
deleteCommand.DataSpace,
deleteCommand.Target,
deleteCommand.Predicate,
setClauses.AsReadOnly(), null);
}
private bool IsPropertyExists(DbModificationCommandTree command, string property)
{
var table = (EntityType)command.Target.VariableType.EdmType;
return table.Properties.Any(p => p.Name == property);
}
}
Create configuration class for register DbInterseptor.
public class CustomDbConfiguration : DbConfiguration
{
public CustomDbConfiguration()
{
AddInterceptor(new SoftDeleteInterseptor());
}
}
public partial class CustomDbContext : DbContext
{
static IDCompleteDbContext()
{
DbConfiguration.SetConfiguration(new CustomDbConfiguration());
}
public virtual DbSet<CommandEntity> CommandEntities { get; set; }
}
public class CommandEntity
{
public int Id {get; set;}
public DateTime? DeletedDate {get; set;}
public string Name {get; set;}
}
When delete entity, entity did not deleted
var context = new CustomDbContext();
var entity = context.CommandEntities.First();
context.CommandEntities.Remove(entity);
context.SubmitChanges();
Did not work. EF provider generate wrong SQL: UPDATE [dbo].[Devices] SET [DeletedDate] = [DeletedDate] IS NULL#0[DeletedDate] WHERE ([Id] = #1) #0: '01.05.2018 7:45:22' (Type = DateTime2) #1: '20' (Type = Int32)

Is running a query from a command a violation of Command-Query Separation?

Given a real-world anonymous shopping cart, the "AddToCart" workflow must do the following steps:
Lookup the current product from the database. Get the price from the product or use a service to calculate the price on user selections and other product properties. (query)
Lookup the current shopping cart from the database. (query)
If the current shopping cart doesn't exist in the database, create a new shopping cart entity (in memory).
Add the new item (product) to the shopping cart entity (in memory) along with its price.
Run any discount calculations on the entire shopping cart. (depends on query)
Run any sales tax calculations on the shopping cart. (depends on query)
Run any shipping calculations on the shopping cart. (depends on query)
If this is a new shopping cart, add the entity to the database, otherwise update the shopping cart in the database. (command)
So, although "AddToCart" sounds like it should be a command (since it updates the system state), in practice it depends on many queries.
My Question
What is the generally accepted way to handle workflows like this?
Make an AddToCartCommandHandler that depends on other services that may run queries.
Make a facade CartService that orchestrates the workflow that runs the queries first followed by the commands.
Make the controller action method first run the queries, then run any commands. Seems like some of the query steps could be missed if this needs to be reused.
Other?
Is the reason I can't find an answer about this because it "depends on the design" and this is one of the exceptions where not to apply it?
If the commands and queries are separated, would I pass my real entity framework entity class to the command that adds/updates the cart (so EF can work out whether it is attached or not)? It seems like a DTO won't do in this case.
NOTE: I am implicitly assuming that systems that implement CQS do so with the aim that eventually they could become a full-on CQRS system. If so, this workflow apparently would not be able to make the transition - hence my question.
Background
I am taking my first stab at CQS.
It is clear from the documentation I have read about this pattern that a query must not change the system state.
However, it is unclear whether it is considered okay to run a query from within a command (I can't seem to find any info anywhere).
There are several real-world cases I can think of where this needs to happen. But, given the lack of real-world examples of this pattern online I am uncertain how to proceed. There is lots of theory online, but the only code I can find is here and here.
The answer to this problem came in the form of a comment by qujck.
The solution is to break the application into different query types and command types. The exact purpose of each type remain a mystery (since the blog post doesn't go into the reasons why he made this distinction), but it does make it clear how top-level and mid-level commands can depend on database queries.
Command Types
Command (top-level)
Command Strategy (mid-level)
Data Command (direct data access)
Query Types
Query (top-level)
Query Strategy (mid-level)
Data Query (direct data access)
Command-Query Implementation
// Commands
public interface ICommand
{
}
public interface IDataCommand
{
}
/// <summary>
/// A holistic abstraction, an abstraction that acts as the whole of each transaction
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
public interface ICommandStrategyHandler<TCommand> where TCommand : ICommand
{
void Handle(TCommand command);
}
/// <summary>
/// Direct database update
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public interface IDataCommandHandler<TCommand> where TCommand : IDataCommand
{
void Handle(TCommand command);
}
// Queries
public interface IQuery<TResult>
{
}
public interface IDataQuery<TResult>
{
}
/// <summary>
/// A holistic abstraction, an abstraction that acts as the whole of each transaction
/// </summary>
/// <typeparam name="TQuery"></typeparam>
/// <typeparam name="TResult"></typeparam>
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
public interface IQueryStrategyHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
/// <summary>
/// Direct database query
/// </summary>
/// <typeparam name="TQuery"></typeparam>
/// <typeparam name="TResult"></typeparam>
public interface IDataQueryHandler<TQuery, TResult> where TQuery : IDataQuery<TResult>
{
TResult Handle(TQuery query);
}
/// <summary>
/// Generic processor that can run any query
/// </summary>
public interface IQueryProcessor
{
TResult Execute<TResult>(IQuery<TResult> query);
// NOTE: Stephen recommends against using Async. He may be right that it is not
// worth the aggrevation of bugs that may be introduced.
//Task<TResult> Execute<TResult>(IQuery<TResult> query);
TResult Execute<TResult>(IDataQuery<TResult> query);
}
AddToCart Dependency Graph
Using the above implementation, the structure of the AddToCart workflow dependency graph looks like this.
AddToCartCommandHandler : ICommandHandler<AddToCartCommand>
GetShoppingCartDetailsQueryHandler : IQueryHandler<GetShoppingCartDetailsQuery, ShoppingCartDetails>
GetShoppingCartQueryStrategyHandler : IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails>
GetShoppingCartDataQueryHandler : IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails>
ApplicationDbContext
CreateShoppingCartDataCommandHandler : IDataCommandHandler<CreateShoppingCartDataCommand>
ApplicationDbContext
UpdateShoppingCartDataCommandHandler : IDataCommandHandler<UpdateShoppingCartDataCommand>
SetItemPriceCommandStrategyHandler : ICommandStrategyHandler<SetItemPriceCommandStrategy>
GetProductDetailsDataQueryHandler : IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails>
ApplicationDbContext
SetTotalsCommandStrategyHandler : ICommandStrategyHandler<SetTotalsCommandStrategy>
SetDiscountsCommandStrategyHandler : ICommandStrategyHandler<SetDiscountsCommandStrategy>
?
SetSalesTaxCommandStrategyHandler : ICommandStrategyHandler<SetSalesTaxCommandStrategy>
Implementation
DTOs
public class ShoppingCartDetails : IOrder
{
private IEnumerable<IOrderItem> items = new List<ShoppingCartItem>();
public Guid Id { get; set; }
public decimal SubtotalDiscounts { get; set; }
public string ShippingPostalCode { get; set; }
public decimal Shipping { get; set; }
public decimal ShippingDiscounts { get; set; }
public decimal SalesTax { get; set; }
public decimal SalesTaxDiscounts { get; set; }
// Declared twice - once for the IOrder interface
// and once so we can get the realized concrete type.
// See: https://stackoverflow.com/questions/15490633/why-cant-i-use-a-compatible-concrete-type-when-implementing-an-interface
public IEnumerable<ShoppingCartItem> Items
{
get { return this.items as IEnumerable<ShoppingCartItem>; }
set { this.items = value; }
}
IEnumerable<IOrderItem> IOrder.Items
{
get { return this.items; }
set { this.items = value; }
}
//public IEnumerable<ShoppingCartNotification> Notifications { get; set; }
//public IEnumerable<ShoppingCartCoupon> Coupons { get; set; } // TODO: Add this to IOrder
}
public class ShoppingCartItem : IOrderItem
{
public ShoppingCartItem()
{
this.Id = Guid.NewGuid();
this.Selections = new Dictionary<string, object>();
}
public Guid Id { get; set; }
public Guid ShoppingCartId { get; set; }
public Guid ProductId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public decimal PriceDiscount { get; set; }
public IDictionary<string, object> Selections { get; set; }
}
public class ProductDetails
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public decimal Discount { get; set; }
}
Calculating Order Totals
Rather than relying on a string of services to do simple (and required) arithmetic, I opted to put this behavior into extension methods so it is done on the fly against the actual data. Since this logic will need to be shared between the shopping cart, order, and quote, the calculation is done against IOrder and IOrderItem rather than concrete model types.
// Contract to share simple cacluation and other business logic between shopping cart, order, and quote
public interface IOrder
{
decimal SubtotalDiscounts { get; set; }
decimal Shipping { get; set; }
decimal ShippingDiscounts { get; set; }
decimal SalesTax { get; set; }
decimal SalesTaxDiscounts { get; set; }
IEnumerable<IOrderItem> Items { get; set; }
}
public interface IOrderItem
{
Guid ProductId { get; set; }
int Quantity { get; set; }
decimal Price { get; set; }
decimal PriceDiscount { get; set; }
IDictionary<string, object> Selections { get; set; }
}
public static class OrderExtensions
{
public static decimal GetSubtotal(this IOrder order)
{
return order.Items.Sum(x => x.GetTotal());
}
public static decimal GetSubtotalBeforeDiscounts(this IOrder order)
{
return order.Items.Sum(x => x.GetTotalBeforeDiscounts());
}
public static decimal GetTotal(this IOrder order)
{
var subtotal = (order.GetSubtotal() - order.SubtotalDiscounts);
var shipping = (order.Shipping - order.ShippingDiscounts);
var salesTax = (order.SalesTax - order.SalesTaxDiscounts);
return (subtotal + shipping + salesTax);
}
}
public static class OrderItemExtensions
{
public static decimal GetTotalBeforeDiscounts(this IOrderItem item)
{
return (item.Price * item.Quantity);
}
public static decimal GetTotal(this IOrderItem item)
{
return (GetTotalBeforeDiscounts(item) - item.PriceDiscount);
}
public static decimal GetDiscountedUnitPrice(this IOrderItem item)
{
return (item.Quantity > 0) ? (GetTotal(item) / item.Quantity) : 0;
}
}
ShoppingCartController
For brevity, we only show the AddToCart action, but this is where other actions against the shopping cart (i.e. remove from cart) would go as well.
public class ShoppingCartController : Controller
{
private readonly IQueryProcessor queryProcessor;
private readonly IAnonymousIdAccessor anonymousIdAccessor;
private readonly ICommandHandler<AddToCartCommand> addToCartHandler;
public ShoppingCartController(
IQueryProcessor queryProcessor,
IAnonymousIdAccessor anonymousIdAccessor,
ICommandHandler<AddToCartCommand> addToCartHandler)
{
if (queryProcessor == null)
throw new ArgumentNullException("queryProcessor");
if (anonymousIdAccessor == null)
throw new ArgumentNullException("anonymousIdAccessor");
if (addToCartHandler == null)
throw new ArgumentNullException("addToCartHandler");
this.queryProcessor = queryProcessor;
this.anonymousIdAccessor = anonymousIdAccessor;
this.addToCartHandler = addToCartHandler;
}
public ActionResult Index()
{
var command = new GetShoppingCartDetailsQuery
{
ShoppingCartId = this.anonymousIdAccessor.AnonymousID
};
ShoppingCartDetails cart = this.queryProcessor.Execute(command);
return View(cart);
}
public ActionResult AddToCart(ItemViewModel model)
{
var command = new AddToCartCommand
{
ProductId = model.Id,
Quantity = model.Qty,
Selections = model.Selections,
ShoppingCartId = this.anonymousIdAccessor.AnonymousID
};
this.addToCartHandler.Handle(command);
// If we execute server side, it should go to the cart page
return RedirectToAction("Index");
}
}
AddToCartCommandHandler
Here is where the main part of the workflow is executed. This command will be called directly from the AddToCart controller action.
public class AddToCartCommandHandler : ICommandHandler<AddToCartCommand>
{
private readonly IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails> getShoppingCartQuery;
private readonly IDataCommandHandler<UpdateShoppingCartDataCommand> updateShoppingCartCommand;
private readonly ICommandStrategyHandler<SetItemPriceCommandStrategy> setItemPriceCommand;
private readonly ICommandStrategyHandler<SetTotalsCommandStrategy> setTotalsCommand;
public AddToCartCommandHandler(
IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails> getShoppingCartCommand,
IDataCommandHandler<UpdateShoppingCartDataCommand> updateShoppingCartCommand,
ICommandStrategyHandler<SetItemPriceCommandStrategy> setItemPriceCommand,
ICommandStrategyHandler<SetTotalsCommandStrategy> setTotalsCommand
)
{
if (getShoppingCartCommand == null)
throw new ArgumentNullException("getShoppingCartCommand");
if (setItemPriceCommand == null)
throw new ArgumentNullException("setItemPriceCommand");
if (updateShoppingCartCommand == null)
throw new ArgumentNullException("updateShoppingCartCommand");
if (setTotalsCommand == null)
throw new ArgumentNullException("setTotalsCommand");
this.getShoppingCartQuery = getShoppingCartCommand;
this.updateShoppingCartCommand = updateShoppingCartCommand;
this.setItemPriceCommand = setItemPriceCommand;
this.setTotalsCommand = setTotalsCommand;
}
public void Handle(AddToCartCommand command)
{
// Get the shopping cart (aggregate root) from the database
var shoppingCart = getShoppingCartQuery.Handle(new GetShoppingCartQueryStrategy { ShoppingCartId = command.ShoppingCartId });
// Create a new shopping cart item
var item = new Contract.DTOs.ShoppingCartItem
{
ShoppingCartId = command.ShoppingCartId,
ProductId = command.ProductId,
Quantity = command.Quantity,
// Dictionary representing the option selections the user made on the UI
Selections = command.Selections
};
// Set the item's price (calculated/retrieved from database query)
setItemPriceCommand.Handle(new SetItemPriceCommandStrategy { ShoppingCartItem = item });
// Add the item to the cart
var items = new List<Contract.DTOs.ShoppingCartItem>(shoppingCart.Items);
items.Add(item);
shoppingCart.Items = items;
// Set the shopping cart totals (sales tax, discounts)
setTotalsCommand.Handle(new SetTotalsCommandStrategy { ShoppingCart = shoppingCart });
// Update the shopping cart details in the database
updateShoppingCartCommand.Handle(new UpdateShoppingCartDataCommand { ShoppingCart = shoppingCart });
}
}
GetShoppingCartQueryStrategyHandler
public class GetShoppingCartQueryStrategyHandler : IQueryStrategyHandler<GetShoppingCartQueryStrategy, ShoppingCartDetails>
{
private readonly IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails> getShoppingCartDataQuery;
private readonly IDataCommandHandler<CreateShoppingCartDataCommand> createShoppingCartDataCommand;
public GetShoppingCartQueryStrategyHandler(
IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails> getShoppingCartDataQuery,
IDataCommandHandler<CreateShoppingCartDataCommand> createShoppingCartDataCommand)
{
if (getShoppingCartDataQuery == null)
throw new ArgumentNullException("getShoppingCartDataQuery");
if (createShoppingCartDataCommand == null)
throw new ArgumentNullException("createShoppingCartDataCommand");
this.getShoppingCartDataQuery = getShoppingCartDataQuery;
this.createShoppingCartDataCommand = createShoppingCartDataCommand;
}
public ShoppingCartDetails Handle(GetShoppingCartQueryStrategy query)
{
var result = this.getShoppingCartDataQuery.Handle(new GetShoppingCartDataQuery { ShoppingCartId = query.ShoppingCartId });
// If there is no shopping cart, create one.
if (result == null)
{
this.createShoppingCartDataCommand.Handle(new CreateShoppingCartDataCommand { ShoppingCartId = query.ShoppingCartId });
result = new ShoppingCartDetails
{
Id = query.ShoppingCartId
};
}
return result;
}
}
GetShoppingCartDataQueryHandler
/// <summary>
/// Data handler to get the shopping cart data (if it exists)
/// </summary>
public class GetShoppingCartDataQueryHandler : IDataQueryHandler<GetShoppingCartDataQuery, ShoppingCartDetails>
{
private readonly IAppContext context;
public GetShoppingCartDataQueryHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public ShoppingCartDetails Handle(GetShoppingCartDataQuery query)
{
return (from shoppingCart in context.ShoppingCarts
where shoppingCart.Id == query.ShoppingCartId
select new ShoppingCartDetails
{
Id = shoppingCart.Id,
SubtotalDiscounts = shoppingCart.SubtotalDiscounts,
ShippingPostalCode = shoppingCart.ShippingPostalCode,
Shipping = shoppingCart.Shipping,
ShippingDiscounts = shoppingCart.ShippingDiscounts,
SalesTax = shoppingCart.SalesTax,
SalesTaxDiscounts = shoppingCart.SalesTaxDiscounts,
Items = shoppingCart.Items.Select(i =>
new Contract.DTOs.ShoppingCartItem
{
Id = i.Id,
ShoppingCartId = i.ShoppingCartId,
ProductId = i.ProductId,
Quantity = i.Quantity,
Price = i.Price,
PriceDiscount = i.PriceDiscount
// TODO: Selections...
})
}).FirstOrDefault();
}
}
CreateShoppingCartDataCommandHandler
public class CreateShoppingCartDataCommandHandler : IDataCommandHandler<CreateShoppingCartDataCommand>
{
private readonly IAppContext context;
public CreateShoppingCartDataCommandHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public void Handle(CreateShoppingCartDataCommand command)
{
var cart = new ShoppingCart
{
Id = command.ShoppingCartId
};
this.context.ShoppingCarts.Add(cart);
this.context.SaveChanges();
}
}
UpdateShoppingCartDataCommandHandler
This updates the shopping cart with all of the changes that the business layer applied.
For the time being, this "command" does a query so it can reconcile the differences between the database and in memory copy. However, it is obviously a violation of the CQS pattern. I plan to make a follow-up question to determine what the best course of action is for change tracking since change tracking and CQS appear to be intimately linked.
public class UpdateShoppingCartDataCommandHandler : IDataCommandHandler<UpdateShoppingCartDataCommand>
{
private readonly IAppContext context;
public UpdateShoppingCartDataCommandHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public void Handle(UpdateShoppingCartDataCommand command)
{
var cart = context.ShoppingCarts
.Include(x => x.Items)
.Single(x => x.Id == command.ShoppingCart.Id);
cart.Id = command.ShoppingCart.Id;
cart.SubtotalDiscounts = command.ShoppingCart.SubtotalDiscounts;
cart.ShippingPostalCode = command.ShoppingCart.ShippingPostalCode;
cart.Shipping = command.ShoppingCart.Shipping;
cart.ShippingDiscounts = command.ShoppingCart.ShippingDiscounts;
cart.SalesTax = command.ShoppingCart.SalesTax;
cart.SalesTaxDiscounts = command.ShoppingCart.SalesTaxDiscounts;
ReconcileShoppingCartItems(cart.Items, command.ShoppingCart.Items, command.ShoppingCart.Id);
// Update the cart with new data
context.SaveChanges();
}
private void ReconcileShoppingCartItems(ICollection<ShoppingCartItem> items, IEnumerable<Contract.DTOs.ShoppingCartItem> itemDtos, Guid shoppingCartId)
{
// remove deleted items
var items2 = new List<ShoppingCartItem>(items);
foreach (var item in items2)
{
if (!itemDtos.Any(x => x.Id == item.Id))
{
context.Entry(item).State = EntityState.Deleted;
}
}
// Add/update items
foreach (var dto in itemDtos)
{
var item = items.FirstOrDefault(x => x.Id == dto.Id);
if (item == null)
{
items.Add(new ShoppingCartItem
{
Id = Guid.NewGuid(),
ShoppingCartId = shoppingCartId,
ProductId = dto.ProductId,
Quantity = dto.Quantity,
Price = dto.Price,
PriceDiscount = dto.PriceDiscount
});
}
else
{
item.ProductId = dto.ProductId;
item.Quantity = dto.Quantity;
item.Price = dto.Price;
item.PriceDiscount = dto.PriceDiscount;
}
}
}
}
SetItemPriceCommandStrategyHandler
public class SetItemPriceCommandStrategyHandler : ICommandStrategyHandler<SetItemPriceCommandStrategy>
{
private readonly IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails> getProductDetailsQuery;
public SetItemPriceCommandStrategyHandler(
IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails> getProductDetailsQuery)
{
if (getProductDetailsQuery == null)
throw new ArgumentNullException("getProductDetailsQuery");
this.getProductDetailsQuery = getProductDetailsQuery;
}
public void Handle(SetItemPriceCommandStrategy command)
{
var shoppingCartItem = command.ShoppingCartItem;
var product = getProductDetailsQuery.Handle(new GetProductDetailsDataQuery { ProductId = shoppingCartItem.ProductId });
// TODO: For products with custom calculations, need to use selections on shopping cart item
// as well as custom formula and pricing points from product to calculate the item price.
shoppingCartItem.Price = product.Price;
}
}
GetProductDetailsDataQueryHandler
public class GetProductDetailsDataQueryHandler : IDataQueryHandler<GetProductDetailsDataQuery, ProductDetails>
{
private readonly IAppContext context;
public GetProductDetailsDataQueryHandler(IAppContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
}
public ProductDetails Handle(GetProductDetailsDataQuery query)
{
return (from product in context.Products
where product.Id == query.ProductId
select new ProductDetails
{
Id = product.Id,
Name = product.Name,
Price = product.Price
}).FirstOrDefault();
}
}
SetTotalsCommandStrategyHandler
public class SetTotalsCommandStrategyHandler : ICommandStrategyHandler<SetTotalsCommandStrategy>
{
private readonly ICommandStrategyHandler<SetDiscountsCommandStrategy> setDiscountsCommand;
private readonly ICommandStrategyHandler<SetSalesTaxCommandStrategy> setSalesTaxCommand;
public SetTotalsCommandStrategyHandler(
ICommandStrategyHandler<SetDiscountsCommandStrategy> setDiscountsCommand,
ICommandStrategyHandler<SetSalesTaxCommandStrategy> setSalesTaxCommand
)
{
if (setDiscountsCommand == null)
throw new ArgumentNullException("setDiscountsCommand");
if (setSalesTaxCommand == null)
throw new ArgumentNullException("setSalesTaxCommand");
this.setDiscountsCommand = setDiscountsCommand;
this.setSalesTaxCommand = setSalesTaxCommand;
}
public void Handle(SetTotalsCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
// Important: Discounts must be calculated before sales tax to ensure the discount is applied
// to the subtotal before tax is calculated.
setDiscountsCommand.Handle(new SetDiscountsCommandStrategy { ShoppingCart = shoppingCart });
setSalesTaxCommand.Handle(new SetSalesTaxCommandStrategy { ShoppingCart = shoppingCart });
}
}
SetDiscountsCommandStrategyHandler
public class SetDiscountsCommandStrategyHandler : ICommandStrategyHandler<SetDiscountsCommandStrategy>
{
public void Handle(SetDiscountsCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
// TODO: Set discounts according to business rules
foreach (var item in shoppingCart.Items)
{
item.PriceDiscount = 0;
}
shoppingCart.SubtotalDiscounts = 0;
shoppingCart.SalesTaxDiscounts = 0;
shoppingCart.ShippingDiscounts = 0;
}
}
SetSalesTaxCommandStrategyHandler
public class SetSalesTaxCommandStrategyHandler : ICommandStrategyHandler<SetSalesTaxCommandStrategy>
{
public void Handle(SetSalesTaxCommandStrategy command)
{
var shoppingCart = command.ShoppingCart;
var postalCode = command.ShoppingCart.ShippingPostalCode;
bool isInCalifornia = !string.IsNullOrEmpty(postalCode) ?
// Matches 90000 to 96200
Regex.IsMatch(postalCode, #"^9(?:[0-5]\d{3}|6[0-1]\d{2}|6200)(?:-?(?:\d{4}))?$") :
false;
if (isInCalifornia)
{
var subtotal = shoppingCart.GetSubtotal();
// Rule for California - charge a flat 7.75% if the zip code is in California
var salesTax = subtotal * 0.0775M;
shoppingCart.SalesTax = salesTax;
}
}
}
Do note that there is no shipping calculation in this workflow. This is primarily because the shipping calculation may depend on external APIs and it may take some time to return. Therefore, I am planning to make the AddToCart workflow a step that runs instantaneously when an item is added and make a CalculateShippingAndTax workflow that happens after the fact that updates the UI again after the totals have been retrieved from their (possibly external) sources, which might take time.
Does this solve the problem? Yes, it does fix the real-world problems I was having when commands need to depend on queries.
However, it feels like this really only separates queries from commands conceptually. Physically, they still depend on one another unless you only look at the IDataCommand and IDataQuery abstractions that only depend on ApplicationDbContext. I am not sure if this is the intent of qujck or not. I am also uncertain if this solves the bigger issue of the design being transferable to CQRS or not, but since it is not something I am planning for I am not that concerned about it.
There are always trade offs to consider between conflicting design principles. The way to resolve it is to look at the underlying reasons behind the principles. In this case, being unable to run a query without running the command is problematic, but being unable to run a command without running the query is generally harmless. As long as there's a way to run the query standalone, I see no reason not to add the query result to the command, especially if done something like this:
QueryResult command()
{
// do command stuff
return query();
}

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).

Error Trying to pass a IEnumerable list with Linq and Mvc Web API

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;
}

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