gpt4 book ai didi

c# - 静态异步类的模拟单元测试和依赖注入(inject)

转载 作者:行者123 更新时间:2023-11-28 21:17:42 26 4
gpt4 key购买 nike

我有一个用于联系人列表的静态异步缓存。在缓存内部,我正在调用我的存储库以从后端获取数据。我想模拟 ContactsRepository,但我需要将存储库作为参数传递并使用依赖注入(inject)。

根据文档,它不会工作,因为我需要一个类的实例来使用依赖注入(inject)。

public interface IContactsCache
{
Task<List<Contact>> GetContactsAsync(int inst, CancellationToken ct);
}

public class ContactsCache : IContactsCache
{
private static readonly object _syncRoot = new object();
private static readonly Dictionary<int, Task<List<Contact>>> _contactsTasks = new Dictionary<int, Task<List<Contact>>>();

public static Task<List<Contact>> GetContactsAsync(int inst)
{
return GetContactsAsync(inst, CancellationToken.None);
}

public static async Task<List<Contact>> GetCodeValuesAsync(int inst, CancellationToken ct)
{
Task<List<Contact>> task;

lock (_syncRoot)
{
if (_contactsTasks.ContainsKey(inst) && (_contactsTasks[inst].IsCanceled || _contactsTasks[inst].IsFaulted))
{
_contactsTasks.Remove(inst);
}

if (!_contactsTasks.ContainsKey(inst))
{
_contactsTasks[inst] = Task.Run(async () =>
{
using (var rep = new ContactsRepository())
{
return await rep.LoadAsync(inst, ct).ConfigureAwait(false);
}
});
}

task = _contactsTasks[inst];
}

var res = await task.ConfigureAwait(false);

lock (_syncRoot)
{
return res != null ? res.ToList() : null;
}
}

Task<List<CodeValue>> IContactsCache.GetContactsAsync(int inst, CancellationToken ct)
{
return GetContactsAsync(inst, ct);
}
}

最后我希望有这种用法,但我不知道如何更改缓存类或任何其他帮助之王都会非常有帮助。

[TestMethod]
public async void GetContactAsync_WhenCalled_ReturnCodeValuesCache()
{
var expected = new List<Contact>
{
new Contact() {Instance = 1, Name = "Test" }
};

var mock = new Mock<IContactsRepository>()
.Setup(x => x.LoadAsync(It.IsAny<int>(), CancellationToken.None))
.ReturnsAsync(new List<Contact>(expected));

var actual = await ContactsCache.GetContactsAsync(It.IsAny<int>(), CancellationToken.None);

CollectionAssert.AreEqual(actual, expected);
}

但它行不通,我不知道如何正确编写单元测试。

我在使用此类存储库的地方有很多此类缓存。在这种情况下,是否有任何标准或最佳实践如何对静态异步缓存进行单元测试以及如何模拟存储库?

最佳答案

通过将缓存设为静态,您关闭了一些大门。

快速而肮脏的解决方案:

因为你不能构造函数注入(inject)你的存储库,下一个最好的办法就是将它传递给你的静态方法。

 public static async Task<List<Contact>> GetCodeValuesAsync(IContactRepository repo, int inst, CancellationToken ct)

如果您这样做,将存储库的生命周期管理上移一个级别可能是更好的主意。换句话说移动 using对调用者的声明:

using(var repo = new ContactRepository())
{
await ContactsCache.GetContactsAsync(repo , It.IsAny<int>(), CancellationToken.None);
}

然后在你的测试中你可以这样做:

var mock = new Mock<IContactsRepository>()
.Setup(x => x.LoadAsync(It.IsAny<int>(), CancellationToken.None))
.ReturnsAsync(new List<Contact>(expected));

var actual = await ContactsCache.GetContactsAsync(mock , It.IsAny<int>(), CancellationToken.None);

首选方案:

我假设您的存储库负责 session 管理(因此使用 IDisposable 接口(interface))。如果有一种方法可以将存储库接口(interface)与某些实现可能需要释放的任何资源分开,您可以转向构造函数注入(inject)方法。

您的代码将如下所示:

public class ContactsCache : IContactsCache
{
private readonly IContactRepository contactRepo;

public ContactsCache(IContactRepository contactRepo)
{
this.contactRepo = contactRepo;
}

// ...
return await this.contactRepo.LoadAsync(inst, ct).ConfigureAwait(false);
// ...
}

您的单元测试将如下所示:

[TestMethod]
public async void GetContactAsync_WhenCalled_ReturnCodeValuesCache()
{
var expected = new List<Contact>
{
new Contact() {Instance = 1, Name = "Test" }
};

var mock = new Mock<IContactsRepository>()
.Setup(x => x.LoadAsync(It.IsAny<int>(), CancellationToken.None))
.ReturnsAsync(new List<Contact>(expected));

var cache = new ContactsCache(mock);

var actual = await cache .GetContactsAsync(It.IsAny<int>(), CancellationToken.None);

CollectionAssert.AreEqual(actual, expected);
}

您还可以考虑反转缓存和存储库之间的依赖关系。换句话说,您的存储库实现可以有一个缓存。这允许您更动态地选择缓存策略。例如,您可以拥有以下任一内容:

var repo = new ContactRepository(new MemoryCache<Contact>())

var repo = new ContactsRepository(new NullCache<Contact>()) <-- 如果您在某些情况下不需要缓存。

这种方法意味着存储库的使用者不需要知道或关心数据的来源。这使您可以在一开始就不需要存储库的情况下测试缓存机制。当然,如果您想测试存储库,则需要为其提供缓存策略。

遵循这种方法还可以让您获得一个相当快速的解决方案,因为您可以用这样的类包装现有的静态缓存:

public class MemoryCache : ICachingStrategy<Contact>
{
public async Task<List<Contact>> GetCodeValuesAsync(int inst, CancellationToken ct) // This comes from the interface
{
return await ContactsCache.GetContactsAsync(inst, ct); // Just forward the call to the existing static cache
}
}

您的存储库需要做一些工作才能使其在访问数据库/文件系统/远程资源之前考虑缓存。

旁注 - 如果你 new up 'dependencies' 你不再进行依赖注入(inject)。

关于c# - 静态异步类的模拟单元测试和依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55459207/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com