web-dev-qa-db-ja.com

Moqで例外をスローするモックIMemoryCache

MoqでIMemoryCacheをモックしようとしています。私はこのエラーを受け取ります:

タイプ 'System.NotSupportedException'の例外がMoq.dllで発生しましたが、ユーザーコードでは処理されませんでした

追加情報:式は、モックされたオブジェクトに属していないメソッドを参照します:x => x.Get <String>(It.IsAny <String>())

私のあざけるコード:

_namespace Iag.Services.SupplierApiTests.Mocks
{
    public static class MockMemoryCacheService
    {
        public static IMemoryCache GetMemoryCache()
        {
            Mock<IMemoryCache> mockMemoryCache = new Mock<IMemoryCache>();
            mockMemoryCache.Setup(x => x.Get<string>(It.IsAny<string>())).Returns("");<---------- **ERROR**
            return mockMemoryCache.Object;
        }
    }
}
_

なぜそのエラーが出るのですか?

これはテスト中のコードです:

_var cachedResponse = _memoryCache.Get<String>(url);
_

ここで__memoryCache_はIMemoryCacheタイプです

上記の_memoryCache.Get<String>(url)をモックしてnullを返すにはどうすればよいですか?

編集_memoryCache.Set<String>(url, response);に対して同じことをするにはどうすればよいですか?何が返されてもかまいません。モックにメソッドを追加するだけで、呼び出されてもスローされません。

私が試したこの質問の答えに行く:

_mockMemoryCache
    .Setup(m => m.CreateEntry(It.IsAny<object>())).Returns(null as ICacheEntry);
_

これは、memoryCache拡張機能では、CreateEntry内でSetを使用していることを示しているためです。しかし、「オブジェクト参照がオブジェクトのインスタンスに設定されていません」というエラーが発生しています。

17

MemoryCacheExtensions.cs のソースコードによると、

_Get<TItem>_拡張メソッドは、以下を使用します

_public static TItem Get<TItem>(this IMemoryCache cache, object key) {
    TItem value;
    cache.TryGetValue<TItem>(key, out value);
    return value;
}

public static bool TryGetValue<TItem>(this IMemoryCache cache, object key, out TItem value) {
    object result;
    if (cache.TryGetValue(key, out result)) {
        value = (TItem)result;
        return true;
    }

    value = default(TItem);
    return false;
}
_

基本的には TryGetValue(Object, out Object) メソッドを使用していることに注意してください。

Moqを使用して拡張メソッドをモックすることができない場合は、拡張メソッドによってアクセスされるインターフェースメンバーをモックしてみてください。

MoqのクイックスタートMockMemoryCacheServiceを更新して、テスト用のTryGetValueメソッドを適切に設定します。

_public static class MockMemoryCacheService {
    public static IMemoryCache GetMemoryCache(object expectedValue) {
        var mockMemoryCache = new Mock<IMemoryCache>();
        mockMemoryCache
            .Setup(x => x.TryGetValue(It.IsAny<object>(), out expectedValue))
            .Returns(true);
        return mockMemoryCache.Object;
    }
}
_

コメントから

(Getの代わりに)TryGetValueをモックする場合、outパラメーターは、宣言されていなくてもobjectとして宣言する必要があります。

例えば:

_int expectedNumber = 1; 
object expectedValue = expectedNumber. 
_

これを行わないと、同じ名前のテンプレート化された拡張メソッドと一致します。

これは、memoryCache.Get<String>(url)をモックしてnullを返すように変更したサービスを使用した例です

_[TestMethod]
public void _IMemoryCacheTestWithMoq() {
    var url = "fakeURL";
    object expected = null;

    var memoryCache = MockMemoryCacheService.GetMemoryCache(expected);

    var cachedResponse = memoryCache.Get<string>(url);

    Assert.IsNull(cachedResponse);
    Assert.AreEqual(expected, cachedResponse);
}
_

更新

このような_Set<>_拡張メソッドにも同じプロセスを適用できます。

_public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value) {
    var entry = cache.CreateEntry(key);
    entry.Value = value;
    entry.Dispose();

    return value;
}
_

このメソッドはCreateEntryメソッドを利用します。これは、同様に作用するICacheEntryを返します。したがって、次の例のように、モックされたエントリを返すようにモックを設定します。

_[TestMethod]
public void _IMemoryCache_Set_With_Moq() {
    var url = "fakeURL";
    var response = "json string";

    var memoryCache = Mock.Of<IMemoryCache>();
    var cachEntry = Mock.Of<ICacheEntry>();

    var mockMemoryCache = Mock.Get(memoryCache);
    mockMemoryCache
        .Setup(m => m.CreateEntry(It.IsAny<object>()))
        .Returns(cachEntry);

    var cachedResponse = memoryCache.Set<string>(url, response);

    Assert.IsNotNull(cachedResponse);
    Assert.AreEqual(response, cachedResponse);
}
_
38
Nkosi

Welrockenが指摘したように、モックしようとしているインターフェイスにGetメソッドはありません。 Nkosiは、ほとんどの人がIMemoryCacheを使用する典型的な使用法である拡張メソッドのソースコードを参考にリンクしています。基本的に、すべての拡張メソッドは、実行中にどこかで3つのインターフェイスメソッドの1つを呼び出します。

何が起こっているのかをすばやく簡単に調べる方法は、モックされた3つのインターフェイスメソッドすべてにコールバックを設定し、ブレークポイントを挿入することです。

テストターゲットメソッドがGetを呼び出していると想定して、Getメソッドの1つを具体的に模擬するには、次のようにその結果を模擬できます。

    delegate void OutDelegate<TIn, TOut>(TIn input, out TOut output);

    [Test]
    public void TestMethod()
    {
        // Arrange
        var _mockMemoryCache = new Mock<IMemoryCache>();
        object whatever;
        _mockMemoryCache
            .Setup(mc => mc.TryGetValue(It.IsAny<object>(), out whatever))
            .Callback(new OutDelegate<object, object>((object k, out object v) =>
                v = new object())) // mocked value here (and/or breakpoint)
            .Returns(true); 

        // Act
        var result = _target.GetValueFromCache("key");

        // Assert
        // ...
    }

編集:私はセッターをモックする方法の例を この答え に追加しました。

3
user1007074

MemoryCacheEntryOptions and .AddExpirationTokenを使用してセットを呼び出す場合は、トークンのリストを持つエントリも必要です。

これは上記の@Nkosiの回答への追加です。例:

// cache by filename: https://jalukadev.blogspot.com/2017/06/cache-dependency-in-aspnet-core.html
var fileInfo = new FileInfo(filePath);
var fileProvider = new PhysicalFileProvider(fileInfo.DirectoryName);
var options = new MemoryCacheEntryOptions();
options.AddExpirationToken(fileProvider.Watch(fileInfo.Name));
this.memoryCache.Set(key, cacheValue, options);

モックには以下を含める必要があります。

// https://github.com/aspnet/Caching/blob/45d42c26b75c2436f2e51f4af755c9ec58f62deb/src/Microsoft.Extensions.Caching.Memory/CacheEntry.cs
var cachEntry = Mock.Of<ICacheEntry>();
Mock.Get(cachEntry).SetupGet(c => c.ExpirationTokens).Returns(new List<IChangeToken>());

var mockMemoryCache = Mock.Get(memoryCache);
mockMemoryCache
    .Setup(m => m.CreateEntry(It.IsAny<object>()))
    .Returns(cachEntry);
2
Aligned

_Microsoft.Extensions.Caching.Memory.IMemoryCache_インターフェイスにはMicrosoft.Extensions.Caching.Memory.IMemoryCache.Get(object)メソッドはありません。あなたが使おうとするものは_Microsoft.Extensions.Caching.Memory.CacheExtensions_にあります。これらの回答を見て、間接的に質問に答えることができます。

Moqを使用して拡張メソッドをモックする方法

Moqを使用した拡張メソッドのモック

また、後で使用するためにMoqを構成する方法にも注意する必要があります。

コードは、Getメソッドが文字列を返し、文字列パラメーターを受け取ることを示しています。テスト構成がテスト全体を通してそれに従っている場合は、問題ありません。ただし、宣言により、Getメソッドはオブジェクトをキーとして受け取ります。したがって、パラメーター述語のコードはIt.IsAny<object>()になります。

2つ目は、nullを返したい場合は、関数が実際に返す型にキャストする必要があることです(例:.Returns((string)null))。これは Moq.Language.IReturns.Returns に他のオーバーロードがあり、コンパイラが参照しようとしているものを決定できないためです。

0
welrocken