Bil*_*ers 17 c# unit-testing moq mocking asp.net-core
我正在使用asp net core 1.0和xunit.
我正在尝试为一些使用的代码编写单元测试IMemoryCache.但是每当我尝试在I中设置一个值时,IMemoryCache我都会得到一个Null参考错误.
我的单元测试代码是这样的:
将IMemoryCache注入到我想要测试的类中.但是,当我尝试在测试中设置缓存中的值时,我得到一个空引用.
public Test GetSystemUnderTest()
{
var mockCache = new Mock<IMemoryCache>();
return new Test(mockCache.Object);
}
[Fact]
public void TestCache()
{
var sut = GetSystemUnderTest();
sut.SetCache("key", "value"); //NULL Reference thrown here
}
Run Code Online (Sandbox Code Playgroud)
这是班级测试......
public class Test
{
private readonly IMemoryCache _memoryCache;
public Test(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public void SetCache(string key, string value)
{
_memoryCache.Set(key, value, new MemoryCacheEntryOptions {SlidingExpiration = TimeSpan.FromHours(1)});
}
}
Run Code Online (Sandbox Code Playgroud)
我的问题是......我需要以IMemoryCache某种方式设置吗?为DefaultValue设置一个值?当IMemoryCache被嘲笑什么是默认值?
Nko*_*osi 16
IMemoryCache.Set是扩展方法,因此不能使用Moq框架进行模拟.
扩展的代码可以在这里找到
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, MemoryCacheEntryOptions options)
{
using (var entry = cache.CreateEntry(key))
{
if (options != null)
{
entry.SetOptions(options);
}
entry.Value = value;
}
return value;
}
Run Code Online (Sandbox Code Playgroud)
对于测试,需要通过扩展方法模拟安全路径以允许其流向完成.在Set其中还调用缓存条目上的扩展方法,因此也必须满足.这可能会很快变得复杂,所以我建议使用具体的实现
//...
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
//...
public Test GetSystemUnderTest() {
var services = new ServiceCollection();
services.AddMemoryCache();
var serviceProvider = services.BuildServiceProvider();
var memoryCache = serviceProvider.GetService<IMemoryCache>();
return new Test(memoryCache);
}
[Fact]
public void TestCache() {
//Arrange
var sut = GetSystemUnderTest();
//Act
sut.SetCache("key", "value");
//Assert
//...
}
Run Code Online (Sandbox Code Playgroud)
所以现在您可以访问功能完整的内存缓存.
小智 10
public sealed class NullMemoryCache : IMemoryCache
{
public ICacheEntry CreateEntry(object key)
{
return new NullCacheEntry() { Key = key };
}
public void Dispose()
{
}
public void Remove(object key)
{
}
public bool TryGetValue(object key, out object value)
{
value = null;
return false;
}
private sealed class NullCacheEntry : ICacheEntry
{
public DateTimeOffset? AbsoluteExpiration { get; set; }
public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
public IList<IChangeToken> ExpirationTokens { get; set; }
public object Key { get; set; }
public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks { get; set; }
public CacheItemPriority Priority { get; set; }
public long? Size { get; set; }
public TimeSpan? SlidingExpiration { get; set; }
public object Value { get; set; }
public void Dispose()
{
}
}
}
Run Code Online (Sandbox Code Playgroud)
向下滚动到代码片段以间接模拟缓存设置器(具有不同的到期属性)
虽然确实不能使用Moq或大多数其他模拟框架直接模拟扩展方法,但它们通常可以间接模拟 - 对于围绕它构建的方法来说肯定是这种情况IMemoryCache
正如我在这个答案中指出的那样,从根本上说,所有扩展方法都会在其执行过程中的某处调用三个接口方法之一。
Nkosi 的 回答提出了非常有效的观点:它会很快变得复杂,您可以使用具体的实现来测试事物。这是一种完全有效的使用方法。然而,严格来说,如果你沿着这条路走下去,你的测试将取决于第三方代码的实现。从理论上讲,对此的更改可能会破坏您的测试 - 在这种情况下,这种情况极不可能发生,因为缓存存储库已存档。
此外,使用具有大量依赖项的具体实现可能会涉及大量开销。如果您每次都创建一组干净的依赖项并且您有许多测试,这可能会给您的构建服务器增加相当大的负载(我不是说这里就是这种情况,这将取决于许多因素)
最后你失去了另一个好处:通过自己调查源代码来模拟正确的东西,你更有可能了解你正在使用的库是如何工作的。因此,您可能会学习如何更好地使用它,并且几乎肯定会学到其他东西。
对于您正在调用的扩展方法,您应该只需要三个带有回调的设置调用来断言调用参数。这可能不适合您,具体取决于您要测试的内容。
[Fact]
public void TestMethod()
{
var expectedKey = "expectedKey";
var expectedValue = "expectedValue";
var expectedMilliseconds = 100;
var mockCache = new Mock<IMemoryCache>();
var mockCacheEntry = new Mock<ICacheEntry>();
string? keyPayload = null;
mockCache
.Setup(mc => mc.CreateEntry(It.IsAny<object>()))
.Callback((object k) => keyPayload = (string)k)
.Returns(mockCacheEntry.Object); // this should address your null reference exception
object? valuePayload = null;
mockCacheEntry
.SetupSet(mce => mce.Value = It.IsAny<object>())
.Callback<object>(v => valuePayload = v);
TimeSpan? expirationPayload = null;
mockCacheEntry
.SetupSet(mce => mce.AbsoluteExpirationRelativeToNow = It.IsAny<TimeSpan?>())
.Callback<TimeSpan?>(dto => expirationPayload = dto);
// Act
var success = _target.SetCacheValue(expectedKey, expectedValue,
new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMilliseconds(expectedMilliseconds)));
// Assert
Assert.True(success);
Assert.Equal("key", keyPayload);
Assert.Equal("expectedValue", valuePayload as string);
Assert.Equal(expirationPayload, TimeSpan.FromMilliseconds(expectedMilliseconds));
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
8715 次 |
| 最近记录: |