问题描述
||
我遇到的问题是我的单元测试失败,当我逐步了解逻辑时,我不知道为什么。我有一个单元测试,该单元测试在查看单个类别的产品(即属于“图书”类别的产品)时失败。
通过测试调试时,我注意到在StoreController.List方法的前几行中返回了零个“ products”。如果调用GetProducts()或GetProductsByCategoryTypeDescription(),则会发生这种情况,但不会返回任何内容。根据我的模拟,数据应该返回并且我对为什么不返回感到困惑。如果需要,我也可以显示我的实体,但是在这种情况下,我认为它们并不重要...
要注意的最大事情是我的测试失败,但是当手动测试应用程序时,它似乎可以正常工作。我认为问题在于我如何潜在地设置我的模拟。
.NET:V 4.0
测试工具:NUnit v2.5
模拟框架:Moq 4.0
DI:Ninject 2.2
ORM:Linq 2 SQL
我的资料库:
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);
}
}
我的控制器出现问题:
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);
}
我的单元测试:
[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,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;
}
}
解决方法
list方法的第一行如下:
public ViewResult List(string category,int page = 1) //Use default value
{
var productsToShow = (category == null)
? productRepository.GetProducts()
: productRepository.GetProductsByCategoryTypeDescription(category);
在测试中调用list方法时,如下所示:
var result = controller.List(\"Sermons\",1);
根据呼叫站点,我们可以看到category
参数不为null。这意味着List方法将跳过productRepository.GetProducts()
调用而改为执行productRepository.GetProductsByCategoryTypeDescription(category)
调用。
查看您的设置存储库模拟的帮助程序,我们看到:
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;
}
由此可见,未在IProductRepository
上设置GetProductsByCategoryTypeDescription
方法,它将返回null或空的IEnumerable<Product>
。您需要显式设置此方法以返回所需的集合。我建议添加一个新的帮助程序,或用以下类似的方法替换现有的帮助程序:
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;
}
然后更改单元测试以使用该助手。将类别变量添加到测试中,以确保\“ Sermons \”值仅在一个位置进行硬编码。
// 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\" } }
);
再次将类别变量用于\“ Act \”步骤:
// Act: ... then when we ask for one specific category
var result = controller.List(category,1);
这样可以解决您的零产品问题。要注意的另一件事是,对于此单元测试,您设置了一个模拟IProductCategoryRepository
,但似乎根本没有使用它。也许有一些未显示的代码需要它。但是如果不是这样,那么您可能应该只使用一个普通的,未初始化的模拟(即将新的Mock<IProductCategoryRepository>().Object
直接传递给控制器构造函数),以明确此特定测试中不涉及该对象。
,仅仅通过查看您发布的代码,我认为您可能会缺少模拟的概念。在查看单元测试时,我们没有理由看到您的依赖关系的具体实现,因为它们是被模拟/存根的。
该测试应该做的就是验证您对控制器与存储库依赖项交互的期望。