c# mocking IFormFile CopyToAsync() 方法

Posted

技术标签:

【中文标题】c# mocking IFormFile CopyToAsync() 方法【英文标题】:c# mocking IFormFile CopyToAsync() method 【发布时间】:2018-04-28 18:35:12 【问题描述】:

我正在为一个将 IFormFile 列表转换为我自己的任意数据库文件类列表的异步函数进行单元测试。

将文件数据转换为字节数组的方法是:

internal async Task<List<File>> ConvertFormFilesToFiles(ICollection<IFormFile> formFiles)

    var file = new File
    
        InsertDateTime = DateTime.Now,
        LastChangeDateTime = DateTime.Now
    ;
    if (formFile.Length > 0)
    
        using (var memoryStream = new MemoryStream())
        
            await formFile.CopyToAsync(memoryStream, CancellationToken.None);
            file.FileData = memoryStream.ToArray();
        
    
    // ...

该函数接收 IFormFiles 的 ICollection,因此它是可模拟的。

现在我有这样的测试代码:

//Arrange
var fileConverter = new FilesConverter();
using (var fileStream = new FileStream("Files/uploadme.txt", FileMode.Open, FileAccess.Read))

    using (var memoryStream = new MemoryStream())
    
        var newMemoryStream = new MemoryStream();
        fileStream.CopyTo(memoryStream);
        FormFileMock.Setup(f => f.CopyToAsync(newMemoryStream, CancellationToken.None)).Returns(Task.CompletedTask);
        // some other setups

        //Act
        var result = await fileConverter.ConvertFormFilesToFiles(new List<IFormFile>  FormFileMock.Object );
        //Assert
        Assert.IsTrue(result.Any());
    

创建 newMemoryStream 变量是因为该函数使用新的空内存流调用 CopyToAsync 方法(我不确定这是否有必要)。

问题在于 await formFile.CopyToAsync(memoryStream, CancellationToken.None) 没有将任何数据复制到 memoryStream。

【问题讨论】:

【参考方案1】:

我知道这可能不受欢迎,因为现在使用模拟框架完全“流行”,但为什么不简单地让框架成为框架并采用简单易行的方式呢?您可以创建 FormFile 而无需任何模拟。真正的交易:

var fileConverter = new FilesConverter(FilesConverterLoggerMock.Object, FileDataMock.Object);

// access to a real file should really not be in a "unit" test, but anyway: 
using (var stream = new MemoryStream(File.ReadAllBytes("Files/uploadme.txt"))

  // create a REAL form file
  var formFile = new FormFile(stream , 0, stream.Length, "name", "filename");

  //Act
  var result = await fileConverter.ConvertFormFilesToFiles(new List<IFormFile>  formFile );

  //Assert
  Assert.IsTrue(result.Any());

【讨论】:

这是一个很好的答案。它消除了设置模拟的所有复杂性。干净,简单,完成工作。 这可行,但我将此答案与@Nkosi 他的答案结合在一起。【参考方案2】:

问题是await formFile.CopyToAsync(memoryStream, CancellationToken.None) 没有将任何数据复制到memoryStream

根据您的设置。实际上不会复制任何内容。

您只需将调用设置为已完成即可返回。没有实现任何实际功能。

在返回任务之前,您需要添加 Callback 以执行一些所需的功能。

FormFileMock
    .Setup(_ => _.CopyToAsync(It.IsAny<Stream>(), CancellationToken.None))
    .Callback<Stream, CancellationToken>((stream, token) => 
        //memory stream in this scope is the one that was populated
        //when you called **fileStream.CopyTo(memoryStream);** in the test
        memoryStream.CopyTo(stream);
    ) 
    .Returns(Task.CompletedTask);

【讨论】:

这可行,但我将此答案与@nvoigt 他的答案结合在一起。 一旦它解决了您的问题。我建议你接受其中之一。恕我直言,他更好【参考方案3】:

我结合了@Nkosi 和@nvoigt 的答案。正如@nvoigt 指出的那样:访问真实文件真的不应该在“单元”测试中。所以我用这样的字节数组替换了文件:

using (var memoryStream = new MemoryStream(new byte[]1,2,3,4))

而不是完整的文件。

我按照@Nkosi 的建议在模拟对象上实现了 .callback

FormFileMock.Setup(f => f.CopyToAsync(It.IsAny<Stream>(), CancellationToken.None))
    .Callback<Stream, CancellationToken>((stream, token) =>
    
        // with memoryStream being the stream from inside the using statement
        memoryStream.CopyTo(stream);
    ).Returns(Task.CompletedTask);

现在它可以工作了。

【讨论】:

以上是关于c# mocking IFormFile CopyToAsync() 方法的主要内容,如果未能解决你的问题,请参考以下文章

在也不用 Mock 数据测试了!直接 COPY 线上流量岂不美哉 !分流神器 — Goreplay 「你值得拥有,炸墙推荐」

在也不用 Mock 数据测试了!直接 COPY 线上流量岂不美哉 !分流神器 — Goreplay 「你值得拥有,炸墙推荐」

c#单元测试:使用Moq框架Mock对象

C#单元测试--如何使用moq.mock进行依赖注入

IFormFile 始终为空(带有 MVC/Razor 的 ASP.NET Core)

为啥 IFormFile 显示为空,我该如何解决?