2017-08-06 15 views
1

私は次のようにの使用HttpContext.Current.Server.MapPathなどFile.ReadAllLinesを作るユニットテストしようとしています方法があります:MVCモッキング(部品番号) - 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を参照してくださいます。