如何设置 Mockobjects 并使用 Moq 单元测试 C# 进行验证?
How to setup Mockobjects and verify using Moq unit testing C#?
我的缓存Class如下:
public class InMemoryCache : ICache
{
private readonly ObjectCache _objCache = MemoryCache.Default;
public void Insert<T>(string cacheKey, T value)
{
_objCache.Add(cacheKey, value, DateTimeOffset.MaxValue);
}
public T Get<T>(string cacheKey)
{
if (cacheKey != null)
return (T)Convert.ChangeType(_objCache.Get(cacheKey), typeof(T));
else
return default(T);
}
public bool Exists<T>(string cacheKey)
{
return _objCache.Get(cacheKey) != null;
}
}
我的界面是
public interface ICache
{
void Insert<T>(string cacheKey, T value);
T Get<T>(string cacheKey);
bool Exists<T>(string cacheKey);
}
我正在使用 CacheFactory 调用我的 InMemoryCache Class:
public class CacheFactory
{
public static ICache Cache { get; set; }
public static void Insert<T>(string cacheKey, T value)
{
Cache.Insert<T>(cacheKey, value);
}
public static T Get<T>(string cacheKey)
{
return Cache.Get<T>(cacheKey);
}
public static bool Exists<T>(string cacheKey)
{
return Cache.Get<T>(cacheKey) != null;
}
}
我尝试了如下所示的模拟对象,但无法获得结果。
[Fact()]
public void CacheManager_Insert_Test()
{
string cacheKey = "GradeList";
Mock<ICache> mockObject = new Mock<ICache>();
List<Grade> grades = new List<Grade>
{
new Grade() {Id = 1, Name = "A*"},
new Grade() {Id = 2, Name = "A"},
new Grade() {Id = 3, Name = "B"},
new Grade() {Id = 4, Name = "C"},
new Grade() {Id = 5, Name = "D"},
new Grade() {Id = 6, Name = "E"}
};
var mockedObjectCache = new Mock<ICache>();
// Setup mock's Set method
mockedObjectCache.Setup(m => m.Insert(cacheKey, grades));
// How to get the Mocked data and verify it here
}
有人可以检查一下我向缓存中插入的数据是否正确吗?另外如何在单元测试起订量中验证缓存数据?我是单元测试的新手。
我无法验证,因为我在我的实现中将数据插入对象缓存 class 但模拟了接口。我想如果我能将两者联系起来,它可能正在验证。不确定,这只是我的猜测。
更新的场景
考虑使用另一个名为 "Convertor" 的 class,它根据缓存中的成绩 ID 检索成绩名称。
public class Convertor
{
// Gets Grade ID and Displays Grade Name
public string GetGradeName(int gradeId)
{
var gradeList = CacheFactory.Cache.Get<List<Grade>>(CacheKeys.Grades);
return gradeList.Where(x => x.Id == gradeId).Select(x => x.Name).FirstOrDefault();
}
}
对于这种情况,我该如何验证呢?由于此方法是使用缓存来检索值,因此我不确定如何在此处使用模拟。
您似乎误解了模拟的用法。使用模拟的原因是测试依赖 classes 而不必担心依赖性。
假设您有以下 class:
public class MyDependentClass {
private readonly ICache _cache;
public MyDependentClass(ICache cache)
{
_cache = cache;
}
public int CountGradesInCache()
{
// Behavior depends on what's in the _cache object
return _cache.Get<List<Grade>>("GradeList").Count;
}
}
为了测试上面的 class 你必须模拟在构造函数中注入到 class 的 ICache
对象。在这种情况下,使用 Mock
:
是明智的
string cacheKey = "GradeList";
Mock<ICache> mockObject = new Mock<ICache>();
List<Grade> grades = new List<Grade>
{
new Grade() {Id = 1, Name = "A*"},
new Grade() {Id = 2, Name = "A"},
new Grade() {Id = 3, Name = "B"},
new Grade() {Id = 4, Name = "C"},
new Grade() {Id = 5, Name = "D"},
new Grade() {Id = 6, Name = "E"}
};
var mockedObjectCache = new Mock<ICache>();
// Setup mock's Set method
mockedObjectCache.Setup(m => m.Get(It.Is<string>(cacheKey))).Return(grades);
// Test that the dependent class acts correctly according to the grades that exist in the cache
var myDependentClass = new myDependentClass(mockedObjectCache.Object);
var gradesCount = myDependentClass.CountGradesInCache();
Assert.AreEqual(6, gradesCount);
我们使用依赖 class 依赖的缓存键 "GradeList" 模拟 ICache
到 return 6 级。我们现在可以测试 MyDependentClass
上的方法是否正确 returns 缓存中的计数。同样,您可以在缓存 return 为空列表或 null
的情况下进行测试,以检查从属 class 的行为在所有情况下是否正确。
在你的例子中你似乎想要测试 InMemoryCache
本身,在这种情况下你不想模拟任何东西。我会简单地编写这样的测试:
var cache = new InMemoryCache();
var list = new List<Grade> { ... };
var cache.Insert("GradeList", list);
var cachedList = cache.Get<List<Grade>>("GradeList");
// Assert that the list retrieved from cache has the expected values.
Updated scenario
对于更新后的场景,您无法模拟缓存,因为它是通过 CacheFactory
上的静态方法检索的。这可能是对依赖项使用依赖注入的最佳理由之一,而不是在 class 中自己实例化它们或使用静态 classes/methods.
要使 Converter
class 可测试,我会做的是将 ICache
注入 class 而不是使用 CacheFactory
:
public class Convertor
{
private readonly ICache _cache;
public Convertor(ICache cache)
{
_cache = cache;
}
public string GetGradeName(int gradeId)
{
var gradeList = _cache.Get<List<Grade>>(CacheKeys.Grades);
return gradeList.Where(x => x.Id == gradeId).Select(x => x.Name).FirstOrDefault();
}
}
现在您可以像我上面描述的那样模拟 ICache
并测试 Convertor
class 的功能。
看来你是概念混淆了
如果您想(单元)测试 InMemoryCache 的 Insert 方法 class,您绝对不应该模拟 InMemoryCache。
Mocking 用于第 3 方 classes 在您要测试的 class 中使用。
事实上你的插入方法也有一个依赖:
private readonly ObjectCache _objCache = MemoryCache.Default;
因此,您应该以某种方式模拟 ObjectCache 实例。
但这次ObjectCache必须继承相关接口才能在Moq lib中使用,即IObjectCache
你应该使用:
var mockObjectCache = new Mock<IObjectCache>();
...
//Verifying:
mockObjectCache.Verify(cache => cache.Add(It.IsAny<string>())).Should().Be(true);
然而 ObjectCache class 继承自 IEnumerable 和 IEnumerable>,因此您不能直接模拟它。如果你真的需要模拟,你应该使用桥接接口(反腐败层)。
我的缓存Class如下:
public class InMemoryCache : ICache
{
private readonly ObjectCache _objCache = MemoryCache.Default;
public void Insert<T>(string cacheKey, T value)
{
_objCache.Add(cacheKey, value, DateTimeOffset.MaxValue);
}
public T Get<T>(string cacheKey)
{
if (cacheKey != null)
return (T)Convert.ChangeType(_objCache.Get(cacheKey), typeof(T));
else
return default(T);
}
public bool Exists<T>(string cacheKey)
{
return _objCache.Get(cacheKey) != null;
}
}
我的界面是
public interface ICache
{
void Insert<T>(string cacheKey, T value);
T Get<T>(string cacheKey);
bool Exists<T>(string cacheKey);
}
我正在使用 CacheFactory 调用我的 InMemoryCache Class:
public class CacheFactory
{
public static ICache Cache { get; set; }
public static void Insert<T>(string cacheKey, T value)
{
Cache.Insert<T>(cacheKey, value);
}
public static T Get<T>(string cacheKey)
{
return Cache.Get<T>(cacheKey);
}
public static bool Exists<T>(string cacheKey)
{
return Cache.Get<T>(cacheKey) != null;
}
}
我尝试了如下所示的模拟对象,但无法获得结果。
[Fact()]
public void CacheManager_Insert_Test()
{
string cacheKey = "GradeList";
Mock<ICache> mockObject = new Mock<ICache>();
List<Grade> grades = new List<Grade>
{
new Grade() {Id = 1, Name = "A*"},
new Grade() {Id = 2, Name = "A"},
new Grade() {Id = 3, Name = "B"},
new Grade() {Id = 4, Name = "C"},
new Grade() {Id = 5, Name = "D"},
new Grade() {Id = 6, Name = "E"}
};
var mockedObjectCache = new Mock<ICache>();
// Setup mock's Set method
mockedObjectCache.Setup(m => m.Insert(cacheKey, grades));
// How to get the Mocked data and verify it here
}
有人可以检查一下我向缓存中插入的数据是否正确吗?另外如何在单元测试起订量中验证缓存数据?我是单元测试的新手。
我无法验证,因为我在我的实现中将数据插入对象缓存 class 但模拟了接口。我想如果我能将两者联系起来,它可能正在验证。不确定,这只是我的猜测。
更新的场景
考虑使用另一个名为 "Convertor" 的 class,它根据缓存中的成绩 ID 检索成绩名称。
public class Convertor
{
// Gets Grade ID and Displays Grade Name
public string GetGradeName(int gradeId)
{
var gradeList = CacheFactory.Cache.Get<List<Grade>>(CacheKeys.Grades);
return gradeList.Where(x => x.Id == gradeId).Select(x => x.Name).FirstOrDefault();
}
}
对于这种情况,我该如何验证呢?由于此方法是使用缓存来检索值,因此我不确定如何在此处使用模拟。
您似乎误解了模拟的用法。使用模拟的原因是测试依赖 classes 而不必担心依赖性。
假设您有以下 class:
public class MyDependentClass {
private readonly ICache _cache;
public MyDependentClass(ICache cache)
{
_cache = cache;
}
public int CountGradesInCache()
{
// Behavior depends on what's in the _cache object
return _cache.Get<List<Grade>>("GradeList").Count;
}
}
为了测试上面的 class 你必须模拟在构造函数中注入到 class 的 ICache
对象。在这种情况下,使用 Mock
:
string cacheKey = "GradeList";
Mock<ICache> mockObject = new Mock<ICache>();
List<Grade> grades = new List<Grade>
{
new Grade() {Id = 1, Name = "A*"},
new Grade() {Id = 2, Name = "A"},
new Grade() {Id = 3, Name = "B"},
new Grade() {Id = 4, Name = "C"},
new Grade() {Id = 5, Name = "D"},
new Grade() {Id = 6, Name = "E"}
};
var mockedObjectCache = new Mock<ICache>();
// Setup mock's Set method
mockedObjectCache.Setup(m => m.Get(It.Is<string>(cacheKey))).Return(grades);
// Test that the dependent class acts correctly according to the grades that exist in the cache
var myDependentClass = new myDependentClass(mockedObjectCache.Object);
var gradesCount = myDependentClass.CountGradesInCache();
Assert.AreEqual(6, gradesCount);
我们使用依赖 class 依赖的缓存键 "GradeList" 模拟 ICache
到 return 6 级。我们现在可以测试 MyDependentClass
上的方法是否正确 returns 缓存中的计数。同样,您可以在缓存 return 为空列表或 null
的情况下进行测试,以检查从属 class 的行为在所有情况下是否正确。
在你的例子中你似乎想要测试 InMemoryCache
本身,在这种情况下你不想模拟任何东西。我会简单地编写这样的测试:
var cache = new InMemoryCache();
var list = new List<Grade> { ... };
var cache.Insert("GradeList", list);
var cachedList = cache.Get<List<Grade>>("GradeList");
// Assert that the list retrieved from cache has the expected values.
Updated scenario
对于更新后的场景,您无法模拟缓存,因为它是通过 CacheFactory
上的静态方法检索的。这可能是对依赖项使用依赖注入的最佳理由之一,而不是在 class 中自己实例化它们或使用静态 classes/methods.
要使 Converter
class 可测试,我会做的是将 ICache
注入 class 而不是使用 CacheFactory
:
public class Convertor
{
private readonly ICache _cache;
public Convertor(ICache cache)
{
_cache = cache;
}
public string GetGradeName(int gradeId)
{
var gradeList = _cache.Get<List<Grade>>(CacheKeys.Grades);
return gradeList.Where(x => x.Id == gradeId).Select(x => x.Name).FirstOrDefault();
}
}
现在您可以像我上面描述的那样模拟 ICache
并测试 Convertor
class 的功能。
看来你是概念混淆了
如果您想(单元)测试 InMemoryCache 的 Insert 方法 class,您绝对不应该模拟 InMemoryCache。
Mocking 用于第 3 方 classes 在您要测试的 class 中使用。
事实上你的插入方法也有一个依赖:
private readonly ObjectCache _objCache = MemoryCache.Default;
因此,您应该以某种方式模拟 ObjectCache 实例。
但这次ObjectCache必须继承相关接口才能在Moq lib中使用,即IObjectCache
你应该使用:
var mockObjectCache = new Mock<IObjectCache>();
...
//Verifying:
mockObjectCache.Verify(cache => cache.Add(It.IsAny<string>())).Should().Be(true);
然而 ObjectCache class 继承自 IEnumerable 和 IEnumerable>,因此您不能直接模拟它。如果你真的需要模拟,你应该使用桥接接口(反腐败层)。