简体   繁体   English

为Epplus Excel文件创建Moq

[英]Create Moq for Epplus Excel file

My first question here. 我的第一个问题。 I have looked my query but could not find a helpful answer. 我看了我的查询但找不到有用的答案。

My task is to write unit test case for my excel file. 我的任务是为我的excel文件编写单元测试用例。 The issue I am facing is that we using Epplus for excel files and I am not sure how can we write unit test cases for this. 我面临的问题是我们使用Epplus for excel文件,我不知道如何为此编写单元测试用例。 I looked up and found that we can also use MOQ to mock up. 我抬起头,发现我们也可以使用MOQ来模拟。 But again I could not find any helpful links for mocking an excel file that uses Epplus. 但是我再也找不到任何有用的链接来模拟使用Epplus的excel文件。 I found this link Unit testing classes that use EPPlus but I am not sure how I can implement this . 我发现这个链接使用EPPlus的单元测试类,但我不确定如何实现它。

I would appreciate if someone can provide a sample of how to write a simple unit test for the excel file. 如果有人能提供如何为excel文件编写简单单元测试的示例,我将不胜感激。 The test can be to check if file uploaded is an excel file or not, checking if the excel is empty or not etc. 测试可以是检查上传的文件是否是excel文件,检查excel是否为空等。

Sorry at this moment I dont have any sample. 对不起,此刻我没有任何样品。 What I can share is the code where I am reading the excel file: 我可以分享的是我正在阅读excel文件的代码:

public class MyController : Controller
{
  [HttpPost("upload")]
  public async Task<IActionResult> UploadFile(IFormFile file)
  {
   JArray data = new JArray();
    using (ExcelPackage package = new ExcelPackage(file.OpenReadStream()))
    {
      ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
      //Check if excel is empty.
      if (worksheet.Dimension == null)
      {
         return BadRequest("File is blank.");
      }
     data = Helper.CreateJson(worksheet);
                }
     return Ok(data);
  }
}

I had created a helper class as: 我创建了一个帮助类:

public static JArray CreateJson(ExcelWorksheet worksheet)
{
  JArray data = new JArray();
  JObject jobject = new JObject();

  int rowCount = worksheet.Dimension.End.Row;
  int colCount = worksheet.Dimension.End.Column;

    for (int row = 1; row <= rowCount; row++)
    {
       for (int col = 1; col <= colCount; col++)
        {
          var value = worksheet.Cells[row, col].Value;
          //Excel has 2 columns and I want to create a json from that.
          if (col == 1)              
          {
             jObject.Add("ID", rowValue.ToString());
          }
          else
          {
             jObject.Add("Name", rowValue.ToString());
          }
        }
         data.Add(jObject);
         jObject= new JObject();
     }
   return data;
}

This is the Test Class i have so far. 这是我到目前为止的测试类。

public class TestClass
{
    private MyController _controller;
    public TestClass()
    {
      _controller = new MyController (); 
    }

      [Fact]
    public void Upload_WhenCalled()
    {
        //var file = new FileInfo(@"C:\myfile.xlsx");
        //...what next?

        var file = new Mock<IFormFile>();
        var content = File.OpenRead(@"C:\myfile.xlsx");

        var result = _controller.UploadFile(file.Object);
        //When I debug it throws error "Object reference not set to an instance of an object."
    }
}

In this case mock IFormFile to return the file stream in your test and pass that to the action under test. 在这种情况下,模拟IFormFile以在测试中返回文件流并将其传递给测试中的操作。 Make sure all other necessary dependencies are satisfied. 确保满足所有其他必要的依赖项。

public class TestClass {
    private MyController _controller;
    public TestClass() {
      _controller = new MyController (); 
    }

    [Fact]
    public void Upload_WhenCalled() {
        //Arrange
        var content = File.OpenRead(@"C:\myfile.xlsx");
        var file = new Mock<IFormFile>();
        file.Setup(_ => _.OpenReadStream()).Returns(content);

        //Act
        var result = _controller.UploadFile(file.Object);

        //Assert
        //...
    }
}

Now while this should help get you through the current problem, you should really take the advice suggested by other answers about abstracting that tight coupling of ExcelPackage out of the controller into its own concern. 现在虽然这应该有助于解决当前的问题,但您应该真正接受其他答案所建议的建议,即将ExcelPackage从控制器中紧密耦合到其自身的关注中。 Would make unit testing the controller easier in isolation. 将单独测试控制器变得更容易。

You could always do an integration test of the wrapper separately as needed. 您可以根据需要单独对包装器进行集成测试。

A simplified example of an interface abstracted from what is currently in the controller 从当前控制器中的内容中抽象出来的简化示例

public interface IExcelService {
    Task<JArray> GetDataAsync(Stream stream);
}

which would have an implementation that mirrors the code in the controller 它将具有镜像控制器中的代码的实现

public class ExcelService: IExcelService {
    public async Task<JArray> GetDataAsync(Stream stream) {
        JArray data = new JArray();
        using (ExcelPackage package = new ExcelPackage(stream)) {
            ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
            if (worksheet.Dimension != null) {
                data = await Task.Run(() => createJson(worksheet));
            }
        }
        return data;
    }

    private JArray createJson(ExcelWorksheet worksheet) {
        JArray data = new JArray();
        int colCount = worksheet.Dimension.End.Column;  //get Column Count
        int rowCount = worksheet.Dimension.End.Row;     //get row count
        for (int row = 1; row <= rowCount; row++) {
            JObject jobject = new JObject();
            for (int col = 1; col <= colCount; col++) {
                var value = worksheet.Cells[row, col].Value;
                //Excel has 2 columns and I want to create a json from that.
                if (col == 1) {
                    jObject.Add("ID", rowValue.ToString());
                } else {
                    jObject.Add("Name", rowValue.ToString());
                }
                data.Add(jObject);
            }
        }
        return data;
    }
}

The controller can now be simplified to follow the Explicit Dependencies Principle 现在可以简化控制器以遵循显式依赖性原则

public class MyController : Controller {
    private readonly IExcelService excel;
    public MyController(IExcelService excel) {
        this.excel = excel;
    }

    [HttpPost("upload")]
    public async Task<IActionResult> UploadFile(IFormFile file) {
        JArray data = await excel.GetDataAsync(myFile.OpenReadStream());
        if(data.Count == 0)
            return BadRequest("File is blank.");
        return Ok(data);
    }
}

You would make sure that the interface and implementation are registered with the Dependency Inversion framework in Startup 您将确保在Startup使用Dependency Inversion框架注册接口和实现

services.AddScoped<IExcelService, ExcelService>();

So now the controller is only concerned with what it is suppose to do when called at run time. 所以现在控制器只关心在运行时调用时要做的事情。 I has no reason to be dealing with implementation concerns 我没有理由处理实施问题

public class MyControllerTests {
    [Fact]
    public async Task Upload_WhenCalled() {
        //Arrange
        var content = new MemoryStream();
        var file = new Mock<IFormFile>();
        file.Setup(_ => _.OpenReadStream()).Returns(content);
        var expected = new JArray();
        var service = new Mock<IExcelService>();
        service
            .Setup(_ => _.GetDataAsync(It.IsAny<Stream>()))
            .ReturnsAsync(expected);

        var controller = new MyController(service.Object);

        //Act
        var result = await controller.UploadFile(file.Object);

        //Assert
        service.Verify(_ => _.GetDataAsync(content));
        //...other assertions like if result is OkContentResult...etc
    }
}

To do an integration test that involves an actual file you can test the service 要执行涉及实际文件的集成测试,您可以测试该服务

public class ExcelServiceTests {
    [Fact]
    public async Task GetData_WhenCalled() {
        //Arrange
        var stream = File.OpenRead(@"C:\myfile.xlsx");
        var service = new ExcelService();

        //Act
        var actual = await service.GetDataAsync(stream);

        //Assert
        //...assert the contents of actual data.
    }
}

Each concern can now be tested on its own. 现在可以单独测试每个问题。

You don't need to mock EPPlus to test. 你不需要模拟EPPlus来测试。 Your focus should be on testing your code, not EPPlus itself. 您的重点应该是测试代码,而不是EPPlus本身。 Just like you wouldn't test any other library you consume. 就像你不会测试你消耗的任何其他库一样。 So have your code generate an Excel file in memory using EPPlus and return it. 因此,您的代码使用EPPlus在内存中生成Excel文件并将其返回。 Then in your test use EPPlus to verify your assertions about the file. 然后在您的测试中使用EPPlus来验证您对该文件的断言。

Here's an example of a pattern to use: 以下是使用模式的示例:

public class MyReportGenerator : IReportGenerator
{
    /* implementation here */
}

public interface IReportGenerator
{
    byte[] GenerateMyReport(ReportParameters parameters);
}

[TestMethod]
public void TestMyReportGenerate()
{
    //arrange
    var parameters = new ReportParameters(/* some values */);
    var reportGenerator = new MyReportGenerator(/* some dependencies */);

    //act
    byte[] resultFile = reportGenerator.GenerateMyReport(parameters);

    //assert
    using(var stream = new MemoryStream(resultFile))
    using(var package = new ExcelPackage(stream))
    {
        //now test that it generated properly, such as:
        package.Workbook.Worksheets["Sheet1"].Cells["C6"].GetValue<decimal>().Should().Be(3.14m);
        package.Workbook.Worksheets["Sheet1"].Column(5).Hidden.Should().BeTrue();
    }  
}

The example above is using the Fluent Assertions library , though obviously this isn't necessary. 上面的示例使用Fluent Assertions库 ,但显然这不是必需的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM