您如何模拟 IAsyncEnumerable?
How do you mock an 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;
}
这实际上取决于您使用的模拟框架。但是,使用 Moq
就像这个例子一样简单
var data = new [] {1,2,3,4};
var mockSvc = new Mock<MyService>();
mockSvc.Setup(obj => obj.CallSomethingReturningAsyncStream()).Returns(data.ToAsyncEnumerable());
我建议按照 Jeroen 的建议使用 System.Linq.Async
中的 ToAsyncEnumerable
。看起来你正在使用最小起订量,所以这看起来像:
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`
}
如果您不想做任何特别的事情,例如延迟的 return 通常是异步枚举的要点,那么您只需创建一个生成器函数即可 return 为您提供值。
public static async IAsyncEnumerable<string> GetTestValues()
{
yield return "foo";
yield return "bar";
await Task.CompletedTask; // to make the compiler warning go away
}
有了它,您可以简单地为您的服务创建一个模拟并测试您的对象:
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]);
当然,既然你现在使用的是生成器函数,你也可以让它变得更复杂,并添加实际的延迟,甚至包括一些控制让步的机制。
解决此问题的一种方法是使用专用测试 类 包装同步枚举的 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);
}
}
用法:
[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
////////
}
我想对调用返回 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;
}
这实际上取决于您使用的模拟框架。但是,使用 Moq
var data = new [] {1,2,3,4};
var mockSvc = new Mock<MyService>();
mockSvc.Setup(obj => obj.CallSomethingReturningAsyncStream()).Returns(data.ToAsyncEnumerable());
我建议按照 Jeroen 的建议使用 System.Linq.Async
中的 ToAsyncEnumerable
。看起来你正在使用最小起订量,所以这看起来像:
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`
}
如果您不想做任何特别的事情,例如延迟的 return 通常是异步枚举的要点,那么您只需创建一个生成器函数即可 return 为您提供值。
public static async IAsyncEnumerable<string> GetTestValues()
{
yield return "foo";
yield return "bar";
await Task.CompletedTask; // to make the compiler warning go away
}
有了它,您可以简单地为您的服务创建一个模拟并测试您的对象:
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]);
当然,既然你现在使用的是生成器函数,你也可以让它变得更复杂,并添加实际的延迟,甚至包括一些控制让步的机制。
解决此问题的一种方法是使用专用测试 类 包装同步枚举的 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);
}
}
用法:
[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
////////
}