T开发者_StackOverflow中文版he problem I'm encountering is that I have a unit test failing and as I walk through the logic I can't figure out why. I have a unit test that fails when viewing products from a single category (i.e. products that are of the category "Books").
As I debug through my test I notice that zero "products" are returned in the first couple lines of the StoreController.List method. This occurs if either GetProducts() or GetProductsByCategoryTypeDescription() is called, nothing is returned. According to my mocks, data should be returning and I'm confused on why it is not. If needed I can also show my entities, but I didn't think they'd matter in this case...
This biggest thing to note is that my test fails, but when manually testing the application it appears to work fine. I believe the issue is with how I'm setting up my mocks potentially.
- .NET: V 4.0
- Testing tool: NUnit v2.5
- Mock Framework: Moq 4.0
- DI: Ninject 2.2
- ORM: Linq 2 Sql
My Repositories:
public class ProductCategoryRepository : IProductCategoryRepository
{
private Table<ProductCategory> productCategoryTable;
private IQueryable<ProductCategory> ProductsCategories
{
get { return productCategoryTable; }
}
public ProductCategoryRepository(string connectionString)
{
productCategoryTable = (new DataContext(connectionString)).GetTable<ProductCategory>();
}
public IQueryable<ProductCategory> GetProductCategories()
{
return ProductsCategories;
}
}
}
public class ProductRepository : IProductRepository
{
private Table<Product> productTable;
private IQueryable<Product> Products
{
get { return productTable; }
}
public ProductRepository(string connectionString)
{
var dc = new DataContext(connectionString);
productTable = dc.GetTable<Product>();
}
public IQueryable<Product> GetProducts()
{
return this.Products;
}
public IQueryable<Product> GetProductsByCategoryTypeDescription(string productCategoryDescription)
{
return this.Products.Where(x => x.ProductCategory.Description == productCategoryDescription);
}
}
My Controller that I'm having the problem with:
public ViewResult List(string category, int page = 1) //Use default value
{
var productsToShow = (category == null)
? productRepository.GetProducts()
: productRepository.GetProductsByCategoryTypeDescription(category);
var viewModel = new ProductListViewModel
{
Products = productsToShow.Skip((page - 1) * this.PageSize)
.Take(PageSize)
.ToList(),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = productsToShow.Count()
},
CurrentCategory = category
};
return View(viewModel);
}
My Unit Test:
[Test]
public void Can_View_Products_From_A_Single_Category()
{
// Arrange: If two products are in two different categories...
IProductRepository productRepository = UnitTestHelpers.MockProductRepository(
new Sermon { Name = "Sermon1", ProductCategory = new ProductCategory { ProductCategoryId = 1, Description = "Sermons" } },
new Sermon { Name = "Sermon2", ProductCategory = new ProductCategory { ProductCategoryId = 2, Description = "Books" } }
);
IProductCategoryRepository productCategoryRepository = UnitTestHelpers.MockProductCategoryRepository(
new ProductCategory { ProductCategoryId = 1, Description = "Sermons" });
var controller = new StoreController(productRepository, productCategoryRepository);
// Act: ... then when we ask for one specific category
var result = controller.List("Sermons", 1);
// Arrange: ... we get only the product from that category
var viewModel = (ProductListViewModel)result.ViewData.Model;
viewModel.Products.Count.ShouldEqual(1);
viewModel.Products[0].Name.ShouldEqual("Sermon1");
viewModel.CurrentCategory.ShouldEqual("Sermons");
}
public static void ShouldEqual<T>(this T actualValue, T expectedValue)
{
Assert.AreEqual(expectedValue, actualValue);
}
public static IProductRepository MockProductRepository(params Product[] products)
{
// Generate an implementer of IProductRepository at runtime using Moq
var mockProductRepos = new Mock<IProductRepository>();
mockProductRepos.Setup(x => x.GetProducts()).Returns(products.AsQueryable());
return mockProductRepos.Object;
}
public static IProductCategoryRepository MockProductCategoryRepository(params ProductCategory[] productCategories)
{
// Generate an implementer of IProductRepository at runtime using Moq
var mockProductCategoryRepos = new Mock<IProductCategoryRepository>();
mockProductCategoryRepos.Setup(x => x.GetProductCategories()).Returns(productCategories.AsQueryable());
return mockProductCategoryRepos.Object;
}
public static void ShouldBeRedirectionTo(this ActionResult actionResult, object expectedRouteValues)
{
var actualValues = ((RedirectToRouteResult)actionResult).RouteValues;
var expectedValues = new RouteValueDictionary(expectedRouteValues);
foreach (string key in expectedValues.Keys)
actualValues[key].ShouldEqual(expectedValues[key]);
}
-
public static class UnitTestHelpers
{
public static void ShouldEqual<T>(this T actualValue, T expectedValue)
{
Assert.AreEqual(expectedValue, actualValue);
}
public static IProductRepository MockProductRepository(params Product[] products)
{
// Generate an implementer of IProductRepository at runtime using Moq
var mockProductRepos = new Mock<IProductRepository>();
mockProductRepos.Setup(x => x.GetProducts()).Returns(products.AsQueryable());
return mockProductRepos.Object;
}
public static IProductCategoryRepository MockProductCategoryRepository(params ProductCategory[] productCategories)
{
// Generate an implementer of IProductRepository at runtime using Moq
var mockProductCategoryRepos = new Mock<IProductCategoryRepository>();
mockProductCategoryRepos.Setup(x => x.GetProductCategories()).Returns(productCategories.AsQueryable());
return mockProductCategoryRepos.Object;
}
}
The first lines of your list method are as follows:
public ViewResult List(string category, int page = 1) //Use default value
{
var productsToShow = (category == null)
? productRepository.GetProducts()
: productRepository.GetProductsByCategoryTypeDescription(category);
When you call the list method in your test, it is as follows:
var result = controller.List("Sermons", 1);
Based on the call site, we can see that the category
parameter is not null. This means that the List method will skip the productRepository.GetProducts()
call and do the productRepository.GetProductsByCategoryTypeDescription(category)
call instead.
Looking at your helper that sets up the repository mock we see this:
public static IProductRepository MockProductRepository(params Product[] products)
{
// Generate an implementer of IProductRepository at runtime using Moq
var mockProductRepos = new Mock<IProductRepository>();
mockProductRepos.Setup(x => x.GetProducts()).Returns(products.AsQueryable());
return mockProductRepos.Object;
}
It's clear from this that the GetProductsByCategoryTypeDescription
method on IProductRepository
is not being set up, and will return either null or an empty IEnumerable<Product>
. You'll need to explicitly set up this method to return the collection you want. I'd recommend either adding a new helper, or replacing the existing one, with something like this:
public static IProductRepository MockProductRepository(string category, params Product[] products)
{
// Generate an implementer of IProductRepository at runtime using Moq
var mockProductRepos = new Mock<IProductRepository>();
mockProductRepos.Setup(x => x.GetProductsByCategoryTypeDescription(category)).Returns(products.AsQueryable());
return mockProductRepos.Object;
}
Then change the unit test to use that helper. Add a category variable to the test to ensure that the "Sermons" value is only hard-coded in one place.
// Arrange: If two products are in two different categories...
string category = "Sermons";
IProductRepository productRepository = UnitTestHelpers.MockProductRepository(
category,
new Sermon { Name = "Sermon1", ProductCategory = new ProductCategory { ProductCategoryId = 1, Description = "Sermons" } },
new Sermon { Name = "Sermon2", ProductCategory = new ProductCategory { ProductCategoryId = 2, Description = "Books" } }
);
Use the category variable again for the "Act" step:
// Act: ... then when we ask for one specific category
var result = controller.List(category, 1);
This should resolve your zero products problem. Another thing to be aware of is that for this unit test you set up a mock IProductCategoryRepository
but then don't appear to use it at all. Maybe there is some code not shown that requires it. But if not, then you should probably just use a plain, uninitialized mock (i.e. pass new Mock<IProductCategoryRepository>().Object
directly to the controller constructor), to be clear that there is no dependency on that object involved in this particular test.
Just by seeing what code you've posted, I think you might be missing the concept of mocking. There's no reason for us to see your concrete implementations of your dependencies when looking at your unit test since they are mocked/stubbed.
All this test should be doing is verifying your expectations of the controller's interactions with the repository dependency.
精彩评论