2017-08-06 90 views
1

我有一個方法,我試圖單元測試,這使得使用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但是我只是無法按照我的實際例子。

我希望有人能夠看看這個,並告訴我如何去實現這種方法的成功測試。我覺得我有很多背景需要,但無法將它們放在一起。

回答

2

在目前的形式下,所討論的方法與單獨測試時很難複製的實現問題緊密耦合。

對於您的示例,我會建議將所有這些實現問題抽象爲它自己的服務。

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只是確保抽象和實現註冊到組合根。

1

採用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的休息。