web-dev-qa-db-fra.com

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

J'écris un test qui dépend des résultats d'une méthode d'extension mais je ne veux pas qu'un échec futur de cette méthode d'extension casse jamais ce test. Se moquer de ce résultat semblait le choix évident, mais Moq ne semble pas offrir un moyen de remplacer une méthode statique (une exigence pour une méthode d'extension). Il existe une idée similaire avec Moq.Protected et Moq.Stub, mais ils ne semblent rien offrir pour ce scénario. Suis-je en train de manquer quelque chose ou dois-je m'y prendre différemment?

Voici un exemple trivial qui échoue avec l'habituel "Attente invalide sur un membre non remplaçable" . C'est un mauvais exemple de la nécessité de se moquer d'une méthode d'extension, mais cela devrait le faire.

public class SomeType {
    int Id { get; set; }
}

var ListMock = new Mock<List<SomeType>>();
ListMock.Expect(l => l.FirstOrDefault(st => st.Id == 5))
        .Returns(new SomeType { Id = 5 });

Comme pour tous les accros de TypeMock qui pourraient suggérer d'utiliser plutôt Isolator: j'apprécie l'effort car il semble que TypeMock pourrait faire le travail les yeux bandés et en état d'ébriété, mais notre budget n'augmentera pas de sitôt.

75
patridge

Les méthodes d'extension ne sont que des méthodes statiques déguisées. Les frameworks de simulation comme Moq ou Rhinomocks ne peuvent créer que des instances de simulation d'objets, ce qui signifie que la simulation de méthodes statiques n'est pas possible.

59
Mendelt

Si vous pouvez modifier le code des méthodes d'extension, vous pouvez le coder comme ceci pour pouvoir tester:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

public static class MyExtensions
{
    public static IMyImplementation Implementation = new MyImplementation();

    public static string MyMethod(this object obj)
    {
        return Implementation.MyMethod(obj);
    }
}

public interface IMyImplementation
{
    string MyMethod(object obj);
}

public class MyImplementation : IMyImplementation
{
    public string MyMethod(object obj)
    {
        return "Hello World!";
    }
}

Ainsi, les méthodes d'extension ne sont qu'une enveloppe autour de l'interface d'implémentation.

(Vous pouvez utiliser uniquement la classe d'implémentation sans méthodes d'extension qui sont en quelque sorte du sucre syntaxique.)

Et vous pouvez simuler l'interface d'implémentation et la définir comme implémentation pour la classe d'extensions.

public class MyClassUsingExtensions
{
    public string ReturnStringForObject(object obj)
    {
        return obj.MyMethod();
    }
}

[TestClass]
public class MyTests
{
    [TestMethod]
    public void MyTest()
    {
        // Given:
        //-------
        var mockMyImplementation = new Mock<IMyImplementation>();

        MyExtensions.Implementation = mockMyImplementation.Object;

        var myObject = new Object();
        var myClassUsingExtensions = new MyClassUsingExtensions();

        // When:
        //-------
        myClassUsingExtensions.ReturnStringForObject(myObject);

        //Then:
        //-------
        // This would fail because you cannot test for the extension method
        //mockMyImplementation.Verify(m => m.MyMethod());

        // This is success because you test for the mocked implementation interface
        mockMyImplementation.Verify(m => m.MyMethod(myObject));
    }
}
21
informatorius

Je sais que cette question n'a pas été active depuis environ un an, mais Microsoft a publié un cadre pour gérer exactement cela appelé Moles .

Voici également quelques tutoriels:

  • DimeCasts.net
  • 15
    Mike Fielden

    J'ai créé une classe wrapper pour les méthodes d'extension dont j'avais besoin pour me moquer.

    public static class MyExtensions
    {
        public static string MyExtension<T>(this T obj)
        {
            return "Hello World!";
        }
    }
    
    public interface IExtensionMethodsWrapper
    {
        string MyExtension<T>(T myObj);
    }
    
    public class ExtensionMethodsWrapper : IExtensionMethodsWrapper
    {
        public string MyExtension<T>(T myObj)
        {
            return myObj.MyExtension();
        }
    }
    

    Ensuite, vous pouvez simuler les méthodes de wrapper dans vos tests et votre code avec votre conteneur IOC.

    12
    ranthonissen

    Pour les méthodes d'extension, j'utilise normalement l'approche suivante:

    public static class MyExtensions
    {
        public static Func<int,int, int> _doSumm = (x, y) => x + y;
    
        public static int Summ(this int x, int y)
        {
            return _doSumm(x, y);
        }
    }
    

    Il permet d'injecter _doSumm assez facilement.

    4
    dmigo