【发布时间】:2023-03-06 12:48:01
【问题描述】:
我一直在尝试对这个简单的方法进行单元测试:
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;
}
【问题讨论】:
-
为什么选择不使用常规的 foreach 循环来实现删除?
-
@Spotted 我喜欢这个 linq foreach 循环的语法,因为它使代码更短。我知道有些人不喜欢它,因为它会使您的代码看起来不那么可读。
-
它也大大降低了代码的可测试性,因此由于您“任意”选择的实现,您现在面临测试问题。我会告诉你来自 Eric Lippert 的 this article,这可能会(或不会)改变你对
foreach的看法。 -
@Spotted 我实际上可能同意你和 Eric Lippert 的观点。但我仍然认为找出如何正确测试此方法很有趣。
-
好的,我会为此写一个答案。
标签: c# unit-testing asynchronous mocking moq