web-dev-qa-db-fra.com

Mock IMemoryCache avec exception de lancement de Moq

J'essaie de me moquer de IMemoryCache avec Moq. Je reçois cette erreur:

Une exception de type 'System.NotSupportedException' s'est produite dans Moq.dll mais n'a pas été gérée dans le code utilisateur

Informations complémentaires: Expression fait référence à une méthode qui N'appartient pas à l'objet simulé: x => x.Get <String> (It.IsAny <String> ())

Mon code moqueur:

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;
        }
    }
}

Pourquoi ai-je cette erreur?

C'est le code sous test:

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

_memoryCache est de type IMemoryCache

Comment puis-je me moquer de la _memoryCache.Get<String>(url) ci-dessus et le laisser retourner null?

Edit : Comment pourrais-je faire la même chose mais pour _memoryCache.Set<String>(url, response);? Peu importe ce qu’il retourne, j’ai juste besoin d’ajouter la méthode à la maquette pour qu’elle ne soit pas lancée à l’appel.

En passant par la réponse à cette question, j'ai essayé:

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

Parce que dans les extensions memoryCache, il montre qu'il utilise CreateEntry à l'intérieur de Set. Mais il y a erreur avec "la référence d'objet non définie sur une instance d'objet".

13
BeniaminoBaggins

Selon le code source de MemoryCacheExtensions.cs ,

La méthode d'extension Get<TItem> utilise les éléments suivants 

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;
}

Notez qu’il utilise essentiellement la méthode TryGetValue(Object, out Object)

Étant donné qu'il n'est pas possible de simuler des méthodes d'extension avec Moq, essayez de vous moquer des membres de l'interface auxquels les méthodes d'extension ont accès.

En vous référant à Le démarrage rapide de Moq update MockMemoryCacheService pour configurer correctement la méthode TryGetValue pour le test.

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;
    }
}

À partir de commentaires

Notez que lorsque vous vous moquez de TryGetValue (au lieu de Get), le paramètre out Doit être déclaré comme object même s'il ne l'est pas. 

Par exemple: 

int expectedNumber = 1; 
object expectedValue = expectedNumber. 

Si vous ne le faites pas, alors elle correspondra à une méthode d'extension basée sur un modèle portant le même nom .

Voici un exemple utilisant le service modifié pour savoir comment se moquer de memoryCache.Get<String>(url) et le laisser retourner 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);
}

METTRE À JOUR

Le même processus peut être appliqué à la méthode d'extension Set<> qui ressemble à ceci.

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

    return value;
}

Cette méthode utilise la méthode CreateEntry qui retourne une ICacheEntry qui est également activée. Donc, configurez la maquette pour renvoyer une entrée simulée aussi bien que dans l'exemple suivant

[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);
}
25
Nkosi

Si vous appelez l'ensemble avec un MemoryCacheEntryOptions and .AddExpirationToken, vous aurez également besoin de l'entrée pour avoir une liste de jetons.

Ceci est un ajout à la réponse de @ Nkosi ci-dessus. Exemple:

// 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);

La maquette doit inclure:

// 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);
1
Aligned

Il n'y a pas de méthode Microsoft.Extensions.Caching.Memory.IMemoryCache.Get(object) dans l'interface Microsoft.Extensions.Caching.Memory.IMemoryCache. Celui que vous essayez d'utiliser se trouve dans le Microsoft.Extensions.Caching.Memory.CacheExtensions. Vous pouvez consulter ces réponses pour répondre directement à votre question.

Comment utiliser Moq pour simuler une méthode d'extension?

Se moquer des méthodes d'extension avec Moq .

Vous devez également savoir comment configurer Moq pour des utilisations ultérieures;

Votre code indique que la méthode Get renvoie une chaîne et prend un paramètre de chaîne. Si votre configuration de test suit cela tout au long du test, tout va bien. Mais par déclaration, la méthode Get prend un objet comme clé. Votre code pour le prédicat de paramètre serait donc It.IsAny<object>()

Deuxièmement, si vous souhaitez renvoyer null, vous devez le transtyper vers le type renvoyé par votre fonction (par exemple, .Returns((string)null)). Cela est dû au fait qu'il existe d'autres surcharges pour Moq.Language.IReturns.Returns et que le compilateur ne peut pas décider à laquelle vous essayez de faire référence.

0
welrocken