web-dev-qa-db-ja.com

Tを指定せずにMoqでジェネリックメソッドをモックする

次のようなメソッドを持つインターフェイスがあります。

public interface IRepo
{
    IA<T> Reserve<T>();
}

このメソッドを使用する可能性のあるタイプごとにSetupメソッドを指定することなく、このメソッドを含むクラスをモックしたいと思います。理想的には、new mock<T>.Object

どうすればこれを達成できますか?

私の説明は不明瞭だったようです。次に例を示します-これは、T(ここでは文字列)を指定するとすぐに可能です。

[TestMethod]
public void ExampleTest()
{
    var mock = new Mock<IRepo>();
    mock.Setup(pa => pa.Reserve<string>()).Returns(new Mock<IA<string>>().Object);
}

私が達成したいのは次のようなものです:

[TestMethod]
public void ExampleTest()
{
    var mock = new Mock<IRepo>();
    mock.Setup(pa => pa.Reserve<T>()).Returns(new Mock<IA<T>>().Object);
    // of course T doesn't exist here. But I would like to specify all types
    // without having to repeat the .Setup(...) line for each of them.
}

テスト対象オブジェクトの一部のメソッドは、3つまたは4つの異なるタイプの予約を呼び出す場合があります。すべてのタイプをセットアップする必要がある場合は、テストごとに多くのセットアップコードを作成する必要があります。しかし、単一のテストでは、それらのすべてに関心はありません。実際にテストしている(およびより複雑なセットアップを喜んで記述している)オブジェクトを除いて、null以外の模擬オブジェクトが必要です。

42
Wilbert

必要なものを誤解していない限り、次のようなメソッドを作成できます。

private Mock<IRepo> MockObject<T>()
{
    var mock = new Mock<IRepo>();
    return mock.Setup(pa => pa.Reserve<T>())
        .Returns(new Mock<IA<T>>().Object).Object;
}
18
Mike Perrenoud

これを行うだけです:

[TestMethod]
public void ExampleTest()
{
  var mock = new Mock<IRepo> { DefaultValue = DefaultValue.Mock, };
  // no setups needed!

  ...
}

あなたのモックはStrictの振る舞いを持たないので、セットアップさえしていない呼び出しに満足します。その場合、「デフォルト」が単に返されます。それから

DefaultValue.Mock

この「デフォルト」が新しいMock<>は、null参照だけではなく、適切なタイプです。

ここでの制限は、返される個々の「サブモック」を制御(たとえば、特別な設定を行う)できないことです。

20

私はあなたが望むものに近づくと思う代替案を見つけました。とにかくそれは私にとって有用だったので、ここに行きます。アイデアは、ほぼ純粋に抽象的であり、インターフェイスを実装する中間クラスを作成することです。抽象的ではない部分は、Moqが処理できない部分です。例えば。

public abstract class RepoFake : IRepo
{
    public IA<T> Reserve<T>()
    {
        return (IA<T>)ReserveProxy(typeof(T));
    }

    // This will be mocked, you can call Setup with it
    public abstract object ReserveProxy(Type t);

    // TODO: add abstract implementations of any other interface members so they can be mocked
}

これで、IRepoの代わりにRepoFakeをモックできます。 ReserveProxyの代わりにReserveにセットアップを書くことを除いて、すべてが同じように機能します。タイプに基づいてアサーションを実行する場合は、コールバックを処理できますが、TypeへのReserveProxyパラメーターは完全にオプションです。

5
OlduwanSteve

これが機能するように見える1つの方法です。 IRepoで使用しているすべてのクラスが単一の基本クラスから継承する場合、このまま使用でき、更新する必要はありません。

public Mock<IRepo> SetupGenericReserve<TBase>() where TBase : class
{
    var mock = new Mock<IRepo>();
    var types = GetDerivedTypes<TBase>();
    var setupMethod = this.GetType().GetMethod("Setup");

    foreach (var type in types)
    {
        var genericMethod = setupMethod.MakeGenericMethod(type)
            .Invoke(null,new[] { mock });
    }

    return mock;
}

public void Setup<TDerived>(Mock<IRepo> mock) where TDerived : class
{
    // Make this return whatever you want. Can also return another mock
    mock.Setup(x => x.Reserve<TDerived>())
        .Returns(new IA<TDerived>());
}

public IEnumerable<Type> GetDerivedTypes<T>() where T : class
{
    var types = new List<Type>();
    var myType = typeof(T);

    var assemblyTypes = myType.GetTypeInfo().Assembly.GetTypes();

    var applicableTypes = assemblyTypes
        .Where(x => x.GetTypeInfo().IsClass 
                && !x.GetTypeInfo().IsAbstract 
                 && x.GetTypeInfo().IsSubclassOf(myType));

    foreach (var type in applicableTypes)
    {
        types.Add(type);
    }

    return types;
}

それ以外の場合、ベースクラスがない場合は、SetupGenericReserveを変更してTBase型パラメーターを使用せずに、セットアップするすべての型のリストを次のように作成できます。

public IEnumerable<Type> Alternate()
{
    return new [] 
    {
        MyClassA.GetType(),
        MyClassB.GetType()
    }
}

注:これはASP.NET Core向けに書かれていますが、GetDerivedTypesメソッドを除く他のバージョンでも動作するはずです。

0
Sam