ASP.NET MVC 2-请告诉我为什么在单元测试中存储库在控制器中不返回任何数据

问题描述

|| 我遇到的问题是我的单元测试失败,当我逐步了解逻辑时,我不知道为什么。我有一个单元测试,该单元测试在查看单个类别的产品(即属于“图书”类别的产品)时失败。 通过测试调试时,我注意到在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
直接传递给控制器​​构造函数),以明确此特定测试中不涉及该对象。     ,仅仅通过查看您发布的代码,我认为您可能会缺少模拟的概念。在查看单元测试时,我们没有理由看到您的依赖关系的具体实现,因为它们是被模拟/存根的。 该测试应该做的就是验证您对控制器与存储库依赖项交互的期望。     

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...