c# - 调用EF Core扩展方法的c#单元测试方法
问题描述
我一直在尝试对这个简单的方法进行单元测试:
public void DeleteAllSettingsLinkedToSoftware(Guid softwareId)
{
_dbContext.Settings.Where(s => s.SoftwareId == softwareId).ForEachAsync(s => s.IsDeleted = true);
_dbContext.SaveChanges();
}
ForEachAsync()
但是,从调用该方法的那一刻起,我很难对该方法进行单元测试。
到目前为止,我已经使用 Moq 来设置 dbContext 以在Where()
执行时返回正确的设置。我的尝试:
Setup(m => m.ForEachAsync(It.IsAny<Action<Setting>>(), CancellationToken.None));
我的问题是:我将如何对ForEachAsync()
方法的调用进行单元测试?
我在网上读到有人说不可能对某些静态方法进行单元测试,如果在我的情况下这是真的,我对尽可能多地测试这种方法的替代方案感到好奇。
编辑
我的完整测试代码:
[TestMethod]
public async Task DeleteAllSettingsLinkedToSoftware_Success()
{
//Arrange
var settings = new List<Setting>
{
new Setting
{
SoftwareId = SoftwareId1
},
new Setting
{
SoftwareId = SoftwareId1
},
new Setting
{
SoftwareId = SoftwareId1
},
new Setting
{
SoftwareId = SoftwareId2
}
}.AsQueryable();
var queryableMockDbSet = GetQueryableMockDbSet(settings.ToList());
queryableMockDbSet.As<IQueryable<Setting>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<Setting>(settings.Provider));
DbContext.Setup(m => m.Settings).Returns(queryableMockDbSet.Object);
_settingData = new SettingData(DbContext.Object, SettingDataLoggerMock.Object);
//Act
var result = await _settingData.DeleteAllSettingsLinkedToSoftwareAsync(SoftwareId1);
//Assert
DbContext.Verify(m => m.Settings);
DbContext.Verify(m => m.SaveChanges());
Assert.AreEqual(4, DbContext.Object.Settings.Count());
Assert.AreEqual(SoftwareId2, DbContext.Object.Settings.First().SoftwareId);
}
我知道我的 Assert 仍然需要更多检查。
GetQueryableMockDbSet 方法:
public static Mock<DbSet<T>> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(s => sourceList.Add(s));
dbSet.Setup(d => d.AddRange(It.IsAny<IEnumerable<T>>())).Callback<IEnumerable<T>>(sourceList.AddRange);
dbSet.Setup(d => d.Remove(It.IsAny<T>())).Callback<T>(s => sourceList.Remove(s));
dbSet.Setup(d => d.RemoveRange(It.IsAny<IEnumerable<T>>())).Callback<IEnumerable<T>>(s =>
{
foreach (var t in s.ToList())
{
sourceList.Remove(t);
}
});
return dbSet;
}
解决方案
你根本不必嘲笑ForEachAsync
。ForEachAsync
返回Task
并正在异步执行这是您问题的根源。
使用async
和await
keywards 来解决您的问题:
public async void DeleteAllSettingsLinkedToSoftware(Guid softwareId)
{
await _dbContext.Settings.Where(s => s.SoftwareId == softwareId)
.ForEachAsync(s => s.IsDeleted = true);
_dbContext.SaveChanges();
}
编辑:
发生新异常是因为提供Provider
的不是IDbAsyncQueryProvider。
Microsoft 实现了此接口的通用版本:TestDbAsyncQueryProvider<TEntity>
. 以下是链接中的实现:
internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal TestDbAsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
return new TestDbAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestDbAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public TestDbAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ }
public TestDbAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
IQueryProvider IQueryable.Provider
{
get { return new TestDbAsyncQueryProvider<T>(this); }
}
}
internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public TestDbAsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
public T Current
{
get { return _inner.Current; }
}
object IDbAsyncEnumerator.Current
{
get { return Current; }
}
}
现在Setup
你必须像这样使用它:
mockSet.As<IQueryable<Setting>>()
.Setup(m => m.Provider)
.Returns(new TestDbAsyncQueryProvider<Setting>(data.Provider));
推荐阅读
- react-native - Android - 在 React 本机中使用 Admob 时出错
- r - 如何为 t 检验压缩多行代码?
- typescript - 如何动态处理类实例化
- video.js - HTML IMA SDK - 自定义点击
- asp.net - SignalR JavaScript 客户端 API 调用连接
- github - 如何将文件链接到不同/多个存储库?
- python-2.7 - Jinja2 中的宏不返回列表?
- spring-boot - 通过 HTTPS 公开 Google 计算引擎或 Kubernetes
- ruby - 制作一个根据可变长度插入空格的函数
- snowflake-cloud-data-platform - 如何在雪花中获取完整的功能表并测试它是否正常工作