MVC嘲讽(MOQ) - HttpContext.Current.Server.MapPath

问题描述:

我有一个方法,我试图单元测试,这使得使用HttpContext.Current.Server.MapPath以及File.ReadAllLines如下:MVC嘲讽(MOQ) - HttpContext.Current.Server.MapPath

public List<ProductItem> GetAllProductsFromCSV() 
{ 
    var productFilePath = HttpContext.Current.Server.MapPath(@"~/CSV/products.csv"); 

    String[] csvData = File.ReadAllLines(productFilePath); 

    List<ProductItem> result = new List<ProductItem>(); 

    foreach (string csvrow in csvData) 
    { 
     var fields = csvrow.Split(','); 
     ProductItem prod = new ProductItem() 
     { 
      ID = Convert.ToInt32(fields[0]), 
      Description = fields[1], 
      Item = fields[2][0], 
      Price = Convert.ToDecimal(fields[3]), 
      ImagePath = fields[4], 
      Barcode = fields[5] 
     }; 
     result.Add(prod); 
    } 
    return result; 
} 

我有一个单元测试设置,其中(按预期)失败:

[TestMethod()] 
public void ProductCSVfileReturnsResult() 
{ 
    ProductsCSV productCSV = new ProductsCSV(); 
    List<ProductItem> result = productCSV.GetAllProductsFromCSV(); 
    Assert.IsNotNull(result); 
} 

因为我已经做了很多的阅读起订量和扶养注射,我只是不似乎能够实现。我也看到了一些方便的答案,如:How to avoid HttpContext.Server.MapPath for Unit Testing Purposes但是我只是无法按照我的实际例子。

我希望有人能够看看这个,并告诉我如何去实现这种方法的成功测试。我觉得我有很多背景需要,但无法将它们放在一起。

在目前的形式下,所讨论的方法与单独测试时很难复制的实现问题紧密耦合。

对于您的示例,我会建议将所有这些实现问题抽象为它自己的服务。

public interface IProductsCsvReader { 
    public string[] ReadAllLines(string virtualPath); 
} 

,并明确注入,作为一个依赖关系到类问题

public class ProductsCSV { 
    private readonly IProductsCsvReader reader; 

    public ProductsCSV(IProductsCsvReader reader) { 
     this.reader = reader; 
    } 

    public List<ProductItem> GetAllProductsFromCSV() { 
     var productFilePath = @"~/CSV/products.csv"; 
     var csvData = reader.ReadAllLines(productFilePath); 
     var result = parseProducts(csvData); 
     return result; 
    } 

    //This method could also eventually be extracted out into its own service 
    private List<ProductItem> parseProducts(String[] csvData) { 
     List<ProductItem> result = new List<ProductItem>(); 
     //The following parsing can be improved via a proper 
     //3rd party csv library but that is out of scope 
     //for this question. 
     foreach (string csvrow in csvData) { 
      var fields = csvrow.Split(','); 
      ProductItem prod = new ProductItem() { 
       ID = Convert.ToInt32(fields[0]), 
       Description = fields[1], 
       Item = fields[2][0], 
       Price = Convert.ToDecimal(fields[3]), 
       ImagePath = fields[4], 
       Barcode = fields[5] 
      }; 
      result.Add(prod); 
     } 
     return result; 
    } 
} 

注意该类现在怎么不关心在哪里,它是如何获取的数据。只有当它被问到时才会获取数据。

这可以进一步简化,但这是超出了这个问题的范围。 (请阅读SOLID原则)

现在您可以灵活地模拟测试的高依赖性,预期行为。

[TestMethod()] 
public void ProductCSVfileReturnsResult() { 
    var csvData = new string[] { 
     "1,description1,Item,2.50,SomePath,BARCODE", 
     "2,description2,Item,2.50,SomePath,BARCODE", 
     "3,description3,Item,2.50,SomePath,BARCODE", 
    }; 
    var mock = new Mock<IProductsCsvReader>(); 
    mock.Setup(_ => _.ReadAllLines(It.IsAny<string>())).Returns(csvData); 
    ProductsCSV productCSV = new ProductsCSV(mock.Object); 
    List<ProductItem> result = productCSV.GetAllProductsFromCSV(); 
    Assert.IsNotNull(result); 
    Assert.AreEqual(csvData.Length, result.Count); 
} 

为了完整起见,这里是依赖项的生产版本的样子。

public class DefaultProductsCsvReader : IProductsCsvReader { 
    public string[] ReadAllLines(string virtualPath) { 
     var productFilePath = HttpContext.Current.Server.MapPath(virtualPath); 
     String[] csvData = File.ReadAllLines(productFilePath); 
     return csvData; 
    } 
} 

使用DI只是确保抽象和实现注册到组合根。

采用HttpContext.Current让你假设productFilePath运行时数据,但实际上却并非如此。这是配置值,因为它在应用程序的生命周期中不会更改。您应该将此值注入到需要它的组件的构造函数中。

如果您使用HttpContext.Current,这显然会造成问题,但您可以拨打HostingEnvironment.MapPath() instead;没有HttpContext要求:

public class ProductReader 
{ 
    private readonly string path; 

    public ProductReader(string path) { 
     this.path = path; 
    } 

    public List<ProductItem> GetAllProductsFromCSV() { ... } 
} 

你可以构造你的类如下:

string productCsvPath = HostingEnvironment.MapPath(@"~/CSV/products.csv"); 

var reader = new ProductReader(productCsvPath); 

这不会File解决了紧耦合的,但我将把Nkosi's excellent answer的休息。