你如何模拟 IAsyncEnumerable?

Tec*_*ing 23 c# unit-testing asp.net-core iasyncenumerable

我想对一个调用另一个服务方法的方法进行单元测试,该方法返回一个IAsyncEnumerable<T>. 我已经创建了我的服务的模拟,我Mock<MyService>想设置这个模拟,但我不知道该怎么做。是否可以 ?是否有其他方法可以对调用重新调整的方法进行单元测试IAsyncEnumerable

public async Task<List<String>> MyMethodIWantToTest()
{
  var results = new List<string>();
  await foreach(var item in _myService.CallSomethingReturningAsyncStream())
  {
    results.Add(item);
  }
  return results;
}
Run Code Online (Sandbox Code Playgroud)

pok*_*oke 33

如果您不想做任何特别的事情,例如延迟返回,这通常是异步枚举的重点,那么您可以创建一个生成器函数来为您返回值。

public static async IAsyncEnumerable<string> GetTestValues()
{
    yield return "foo";
    yield return "bar";

    await Task.CompletedTask; // to make the compiler warning go away
}
Run Code Online (Sandbox Code Playgroud)

有了它,您可以简单地为您的服务创建一个模拟并测试您的对象:

var serviceMock = new Mock<IMyService>();
serviceMock.Setup(s => s.CallSomethingReturningAsyncStream()).Returns(GetTestValues);

var thing = new Thing(serviceMock.Object);
var result = await thing.MyMethodIWantToTest();
Assert.Equal("foo", result[0]);
Assert.Equal("bar", result[1]);
Run Code Online (Sandbox Code Playgroud)

当然,由于您现在正在使用生成器函数,您还可以使其更复杂并添加实际延迟,甚至包括一些控制屈服的机制。


Ste*_*ary 30

我建议使用ToAsyncEnumerablefrom System.Linq.Async,正如 Jeroen 建议的那样。看起来你正在使用 Moq,所以这看起来像:

async Task MyTest()
{
  var mock = new Mock<MyService>();
  var mockData = new[] { "first", "second" };
  mock.Setup(x => x.CallSomethingReturningAsyncStream()).Returns(mockData.ToAsyncEnumerable());

  var sut = new SystemUnderTest(mock.Object);
  var result = await sut.MyMethodIWantToTest();

  // TODO: verify `result`
}
Run Code Online (Sandbox Code Playgroud)

  • 赞成教我有关“.ToAsyncEnumerable()”的知识。谢谢。 (4认同)
  • 效果也很好,我投票了,但选择了不需要另一个 nuget 的答案。我知道 System.Linq.Async 但没有意识到他们提供了如此有趣的方法。感谢您让我们知道 :) (2认同)

Tra*_*ore 8

这实际上取决于您使用的模拟框架。但是,这会像这个例子一样简单,使用Moq

var data = new [] {1,2,3,4};
var mockSvc = new Mock<MyService>();
mockSvc.Setup(obj => obj.CallSomethingReturningAsyncStream()).Returns(data.ToAsyncEnumerable());
Run Code Online (Sandbox Code Playgroud)


sil*_*ire 5

解决此问题的一种方法是使用专用测试类来包装IEnumerable同步枚举的对象。

TestAsyncEnumerable.cs

internal class TestAsyncEnumerable<T> : List<T>, IAsyncEnumerable<T>
{
   public TestAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { }

   public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) => new TestAsyncEnumerator<T>(GetEnumerator());
}

internal class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
   private readonly IEnumerator<T> _inner;

   public TestAsyncEnumerator(IEnumerator<T> inner)
   {
      _inner = inner;
   }

   public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(_inner.MoveNext());

   public T Current => _inner.Current;

   public ValueTask DisposeAsync()
   {
      _inner.Dispose();

      return new ValueTask(Task.CompletedTask);
   }
}
Run Code Online (Sandbox Code Playgroud)

用法:

[Fact]
public async Task MyTest() {
   var myItemRepository = A.Fake<IMyItemRepository>();

   A.CallTo(       () => myRepository.GetAll())
    .ReturnsLazily(() => new TestAsyncEnumerable<MyItem>(new List<MyItem> { new MyItem(), ... }));


   //////////////////
   /// ACT & ASSERT
   ////////
}
Run Code Online (Sandbox Code Playgroud)