C# 模拟静态异步类的单元测试和依赖注入

C# 模拟静态异步类的单元测试和依赖注入,c#,.net,unit-testing,testing,C#,.net,Unit Testing,Testing,我有一个联系人列表的静态异步缓存。在缓存内部,我调用我的存储库从后端获取数据。我想模拟ContactsRepository,但我需要将存储库作为参数传递,并使用依赖项注入 根据文档,它不会工作,因为我需要一个类的实例来使用依赖项注入 public interface IContactsCache { Task<List<Contact>> GetContactsAsync(int inst, CancellationToken ct); } public cla

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

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

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);
    }
}
公共接口IContactsCache
{
任务GetContactsAsync(int inst,CancellationToken ct);
}
公共类ContactsCache:IContactsCache
{
私有静态只读对象_syncRoot=新对象();
专用静态只读词典_contactsTasks=new Dictionary();
公共静态任务GetContactsAsync(int inst)
{
返回GetContactsAsync(inst,CancellationToken.None);
}
公共静态异步任务GetCodeValuesAsync(int inst,CancellationToken ct)
{
任务;
锁定(\u syncRoot)
{
如果(_contactsTasks.ContainsKey(inst)&(_contactsTasks[inst].IsCanceled | | | u contactsTasks[inst].IsFaulted))
{
_拆下接触器(安装);
}
如果(!_contactsTasks.ContainsKey(inst))
{
_contactsTasks[inst]=任务。运行(异步()=>
{
使用(var rep=new contacts repository())
{
返回wait rep.LoadAsync(inst,ct).configurewait(false);
}
});
}
任务=_contactsTasks[inst];
}
var res=等待任务。配置等待(false);
锁定(\u syncRoot)
{
return res!=null?res.ToList():null;
}
}
任务IContactsCache.GetContactsAsync(int inst,CancellationToken ct)
{
返回GetContactsAsync(仪器、ct);
}
}
最后,我希望有这种用法,但我不知道如何更改cache类,否则任何其他帮助都会非常有用

[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);
}
[TestMethod]
public async void GetContactAsync\u调用时\u ReturnCodeValuesCache()
{
var预期值=新列表
{
新联系人(){Instance=1,Name=“Test”}
};
var mock=new mock()
.Setup(x=>x.LoadAsync(It.IsAny(),CancellationToken.None))
.ReturnsAsync(新列表(预期));
var actual=await ContactsCache.GetContactsAsync(It.IsAny(),CancellationToken.None);
收款资产等于(实际、预期);
}
但它不能工作,我不知道如何正确地编写单元测试


我有很多这样的缓存,我正在使用这样的存储库。在这种情况下,有没有标准或最佳实践来测试静态异步缓存以及如何模拟存储库?

通过使缓存为静态,您已经关闭了一些大门

快速而肮脏的解决方案:

因为您不能将构造函数注入存储库,所以下一个最好的方法是将其传递给静态方法

 public static async Task<List<Contact>> GetCodeValuesAsync(IContactRepository repo, int inst, CancellationToken ct)
然后在测试中,您将能够执行以下操作:

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);
您的单元测试将如下所示:

[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);
}
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
    }
}
[TestMethod]
public async void GetContactAsync\u调用时\u ReturnCodeValuesCache()
{
var预期值=新列表
{
新联系人(){Instance=1,Name=“Test”}
};
var mock=new mock()
.Setup(x=>x.LoadAsync(It.IsAny(),CancellationToken.None))
.ReturnsAsync(新列表(预期));
var缓存=新的ContactsCache(模拟);
var actual=wait cache.GetContactsAsync(It.IsAny(),CancellationToken.None);
收款资产等于(实际、预期);
}
您也可以考虑反转缓存和存储库之间的依赖关系。换句话说,您的存储库实现可以有一个缓存。这允许您更动态地选择缓存策略。例如,您可以具有以下任一项:

public class ContactsCache : IContactsCache
{
    private readonly IContactRepository contactRepo;

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

    // ...
    return await this.contactRepo.LoadAsync(inst, ct).ConfigureAwait(false);
    // ...
}
var repo=new ContactRepository(new MemoryCache())


var repo=new contacts repository(new NullCache())
ContactsCache是否需要是静态的?您使用的模拟框架是什么?看起来像是Moq,但我不想在这个问题上加上错误的标签……为什么要让这个类是静态的?让它成为一门普通的课。DependencyInjection的整个工作就是为任何需要它的代码提供缓存类实例。是的,我使用的是MoqI,它需要静态才能在许多地方使用。它是用于缓存目的的,让它保持静态非常有用。我可以成为辛格尔顿,但不确定这是否有帮助。
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
    }
}