MVC模拟(Moq)-HttpContext.Current.Server.MapPath [英] MVC Mocking (Moq) - HttpContext.Current.Server.MapPath

查看:94
本文介绍了MVC模拟(Moq)-HttpContext.Current.Server.MapPath的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一种方法正在尝试进行单元测试,该方法使用 HttpContext.Current.Server.MapPath 以及 File.ReadAllLines 如下:

I have a method I am attempting to Unit Test which makes use of HttpContext.Current.Server.MapPath as well as File.ReadAllLines as follows:

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;
}

我有一个单元测试设置,该设置(如预期的那样)失败:

I have a Unit Test setup which (as expected) fails:

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

此后,我对Moq和依赖注入进行了很多阅读,但我似乎无法实现.我还在SO上看到了一些方便的答案,例如:

I have since done a lot of reading on Moq and Dependancy Injection which I just dont seem to be able to implement. I have also seen a few handy answers on SO such as: How to avoid HttpContext.Server.MapPath for Unit Testing Purposes however I am just unable to follow it for my actual example.

我希望有人能够看一下这个问题,并确切地告诉我如何进行此方法的成功测试.我觉得我有很多背景知识,但无法将所有背景知识融为一体.

I am hoping someone is able to take a look at this and tell me exactly how I might go about implementing a successful test for this method. I feel I have a lot of the background required but am unable to pull it all together.

推荐答案

在当前形式下,所讨论的方法与实现问题紧密相关,而在进行隔离测试时,这些问题很难复制.

In its current form, the method in question is too tightly coupled to implementation concerns that are difficult to replicate when testing in isolation.

以您的示例为例,我建议将所有这些实现关注点抽象到其自己的服务中.

For your example, I would advise abstracting all those implementation concerns out into its own service.

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

并明确地将其作为依赖项注入到相关的类中

And explicitly inject that as a dependency into the class in question

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;
    }
}

请注意,该类现在如何与在何处或如何获取数据无关.仅当它被询问时才获取数据.

Note how the class now is not concerned with where or how it gets the data. Only that it gets the data when asked.

这可以进一步简化,但这不在此问题的范围内. (阅读SOLID原理)

This could be simplified even further but that is outside of the scope of this question. (Read up on SOLID principles)

现在,您可以灵活地模拟依赖关系,以进行较高级别的预期行为的测试.

Now you have the flexibility to mock the dependency for testing at a high level, expected behavior.

[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);
}

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

For completeness here is what a production version of the dependency could look like.

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

使用DI只是确保抽象和实现已在组合根目录下注册.

Using DI just make sure that the abstraction and the implementation are registered with the composition root.

这篇关于MVC模拟(Moq)-HttpContext.Current.Server.MapPath的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆