web-dev-qa-db-fra.com

Mocking appel de méthode générique pour tout paramètre de type donné

J'ai une interface

public interface IDataProvider
{
    T GetDataDocument<T>(Guid document) where T:class, new()
}

Je voudrais me moquer d'une manière, qu'il retournerait simplement une nouvelle instance d'un type donné, quel que soit le type exact, quelque chose comme:

myMock.Setup(m => m.GetDataDocument<It.IsAny<Type>()>(It.IsAny<Guid>()))
.Returns(() => new T());

(ce qui ne fonctionne pas bien sûr, car je ne peux pas simplement donner n'importe quel paramètre de type à moq, et je ne peux pas savoir quel type doit être retourné.

Des idées sur celui-ci?

49
Hassan

Au lieu d'utiliser une maquette, votre cas serait peut-être préférable d'utiliser un Stub .

public class StubDataProvider : IDataProvider
{
    public T GetDataDocument<T>(Guid document) where T : class, new()
    {
        return new T();
    }
}

Si vous avez vraiment besoin d'une maquette (vous pouvez donc vérifier que GetDataDocument a été appelé). Au lieu d'essayer de lutter avec un framework Mocking, il est parfois plus facile de simplement créer une classe Mock.

public class MockDataProvider : IDataProvider
{
    private readonly Action _action;

    public MockDataProvider(Action action)
    {
        _action = action;
    }

    public T GetDataDocument<T>(Guid document) where T : class, new()
    {
        _action();
        return new T();
    }
}

Et que dans votre test:

bool wasCalled = false;
IDataProvider dataProvider = new MockDataProvider(() => { wasCalled = true; });
var aTable = dataProvider.GetDataDocument<ATable>(new Guid());
Debug.Assert(wasCalled);
29
Mark Coleman

Pour le test particulier pour lequel vous allez utiliser cette maquette, vous savez probablement ce que sera T, non?

il suffit de configurer la maquette pour cela:

myMock.Setup(m => m.GetDataDocument<MyDataClass>()>(It.IsAny<Guid>()))
   .Returns(() => new MyDataClass());

Il n'est pas vraiment recommandé de réutiliser les simulateurs de toute façon, alors allez-y et configurez les simulateurs pour le test réel à portée de main.

11
Mikael Östberg

J'ai eu un problème similaire, j'ai choisi de ne pas utiliser de stub dans cette situation car je ne voulais pas que des ajouts à l'interface testée nécessitent des modifications immédiates du code de test. c'est-à-dire que l'ajout d'une nouvelle méthode ne devrait pas casser mes tests existants.

Pour que la maquette fonctionne, j'ai ajouté tous les types publics dans un assembly donné au moment de l'exécution.

//This is fairly expensive so cache the types
static DummyRepository()
{
    foreach( var type in typeof( SomeTypeInAssemblyWithModelObjects ).Assembly.GetTypes() )
    {
        if( !type.IsClass | type.IsAbstract || !type.IsPublic || type.IsGenericTypeDefinition )
        {
            continue;
        }

        g_types.Add( type );
    }
}

public DummyRepository()
{
    MockRepository = new Mock<ISomeRepository>();

    var setupLoadBy = GetType().GetMethod( "SetupLoadBy", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod );

    foreach( var type in g_types )
    {
        var loadMethod = setupLoadBy.MakeGenericMethod( type );
        loadMethod.Invoke( this, null );
    }
}

private void SetupLoadBy<T>()
{
    MockRepository.Setup( u => u.Load<T>( It.IsAny<long>() ) ).Returns<long>( LoadById<T> );
}

public T LoadById<T>( long id )
{
}
6
Andre