1

MockRepository以下のようにto を引き続き使用できるように、Moq に複数の呼び出しを期待するように指示するにはどうすればよいVerifyAllですか?

[TestFixture]
public class TestClass
{
    [SetUp]
    public void SetUp()
    {
        _mockRepository = new MockRepository(MockBehavior.Strict);
        _mockThing = _mockRepository.Create<IThing>();

        _sut = new Sut(_mockThing.Object);
    }

    [TearDown]
    public void TearDown()
    {
        _mockRepository.VerifyAll();
    }

    private Mock<IThing> _mockThing;

    private MockRepository _mockRepository;

    [Test]
    public void TestManyCalls(Cell cell)
    {
       _mockThing.SetUp(x => x.DoSomething()).Return(new DoneStatus());
    }
}

検証時にこれを実行できることはわかっていますが、その後はすべてを個別に検証する必要があります。イベント後に検証するのではなく、何を期待するかを伝える必要はありますか?

次のようなもの:

_mockThing.SetUp(x => x.DoSomething()).Return(new DoneStatus()).Times(20);

基本的に、複数の場所に期待するのではなく、テストの開始時にすべての期待を設定したいと思います。

4

1 に答える 1

1

MockRepository はさておき、必要Mockな機能を提供するために を継承するクラスを作成しました。まず、使用法 (XUnit 構文):

    [Fact]
    public void SomeTest()
    {
        var mock = new Mock2<IDependency>();
        var sut = new Sut(mock.Object);
        mock.SetupAndExpect(d => d.DoSomething(It.IsAny<string>(), It.IsAny<long>()),Times.Once).Returns(3);
        mock.SetupAndExpect(d => d.DoSomethingNoReturn(It.IsAny<string>()), Times.Once);
        mock.SetupAndExpect(d => d.DoSomethingNoReturn(It.IsAny<string>()), Times.Once);
        sut.CallDoSomething();
        mock.VerifyAllExpectations();
    }

このメソッドは、渡されるSetupAndExpectallow の代替です。はと同等です。必要に応じて、これらの名前をいじることができます。SetupTimesVerifyAllExpectationsVerifyAll

このMock2クラスはexpressiontimes渡された と を保存してSetupAndExpect、後で使用できるようにしVerifyAllExpectationsます。

コードを示して解決策Mock2について話す前にMockRepository、冗長性について説明します。式はすべて、モック化された型に対してジェネリックな型を持っているため、戻り値のないモック化されたメソッドに対して動作させるのはかなり簡単でした。ただし、戻り値を持つメソッドの場合、呼び出される基礎となる検証は ですMock.Verify<TResult>(...)。正しく閉じられたメソッドにバインドできるようにするために、VerifyAllExpectationsリフレクションを使用することになりました。Mock 自体を変更してこの機能を組み込むことで、ハックの少ないソリューションが可能になると確信しています。

さて、リポジトリに戻ります。私の考えでは、Mock2から継承するのではなくMock、 のインスタンスをMockコンストラクターのパラメーターとして受け取り、それを使用して と を呼び出すSetupように変更することを考えていますVerify。次に、元のインスタンスを呼び出し、作成されたインスタンスをインスタンスのコンストラクターに渡し、それを返す新しい拡張メソッドをMockRepository( Create2??)に記述できます。MockRepository.CreateMockMock2

これに対する最終的な代替手段は、拡張メソッドとしてSetupAndExpectとを に追加することです。ただし、期待情報の保存は、おそらく何らかの静的な状態にする必要があり、クリーンアップの問題に直面するでしょう。VerifyAllExpectationsMock

Mock2コードは次のとおりです。

public class Mock2<T> : Mock<T> where T:class
{
    private readonly Dictionary<Type, List<Tuple<Expression,Func<Times>>>> _resultTypeKeyedVerifications =
        new Dictionary<Type, List<Tuple<Expression, Func<Times>>>>();

    private readonly List<Tuple<Expression<Action<T>>, Func<Times>>> _noReturnTypeVerifications = 
        new List<Tuple<Expression<Action<T>>, Func<Times>>>();

    public ISetup<T, TResult> SetupAndExpect<TResult>(Expression<Func<T, TResult>> expression, Func<Times> times)
    {
        // Store the expression for verifying in VerifyAllExpectations
        var verificationsForType = GetVerificationsForType(typeof(TResult));
        verificationsForType.Add(new Tuple<Expression, Func<Times>>(expression, times));

        // Continue with normal setup
        return Setup(expression);
    }

    public ISetup<T> SetupAndExpect(Expression<Action<T>> expression, Func<Times> times)
    {
        _noReturnTypeVerifications.Add(new Tuple<Expression<Action<T>>, Func<Times>>(expression, times));
        return Setup(expression);
    }

    private List<Tuple<Expression, Func<Times>>> GetVerificationsForType(Type type)
    {
        // Simply gets a list of verification info for a particular return type,
        // creating it and putting it in the dictionary if it doesn't exist.
        if (!_resultTypeKeyedVerifications.ContainsKey(type))
        {
            var verificationsForType = new List<Tuple<Expression, Func<Times>>>();
            _resultTypeKeyedVerifications.Add(type, verificationsForType);
        }
        return _resultTypeKeyedVerifications[type];
    }

    /// <summary>
    /// Use this instead of VerifyAll for setups perfomed using SetupAndRespect
    /// </summary>
    public void VerifyAllExpectations()
    {
        VerifyAllWithoutReturnType();
        VerifyAllWithReturnType();
    }

    private void VerifyAllWithoutReturnType()
    {
        foreach (var noReturnTypeVerification in _noReturnTypeVerifications)
        {
            var expression = noReturnTypeVerification.Item1;
            var times = noReturnTypeVerification.Item2;
            Verify(expression, times);
        }
    }

    private void VerifyAllWithReturnType()
    {
        foreach (var typeAndVerifications in _resultTypeKeyedVerifications)
        {
            var returnType = typeAndVerifications.Key;
            var verifications = typeAndVerifications.Value;

            foreach (var verification in verifications)
            {
                var expression = verification.Item1;
                var times = verification.Item2;

                // Use reflection to find the Verify method that takes an Expression of Func of T, TResult
                var verifyFuncMethod = GetType()
                    .GetMethods(BindingFlags.Instance | BindingFlags.Public)
                    .Single(IsVerifyMethodForReturnTypeAndFuncOfTimes)
                    .MakeGenericMethod(returnType);

                // Equivalent to Verify(expression, times)
                verifyFuncMethod.Invoke(this, new object[] {expression, times});
            }
        }
    }

    private static bool IsVerifyMethodForReturnTypeAndFuncOfTimes(MethodInfo m)
    {
        if (m.Name != "Verify") return false;
        // Look for the single overload with two funcs, which is the one we want
        // as we're looking at verifications for functions, not actions, and the
        // overload we're looking for takes a Func<Times> as the second parameter
        var parameters = m.GetParameters();
        return parameters.Length == 2
               && parameters[0] // expression
                   .ParameterType // Expression
                   .GenericTypeArguments[0] // Func
                   .Name == "Func`2"
               && parameters[1] // times
                   .ParameterType // Func
                   .Name == "Func`1";
    }
}

最終警告: これは軽くテストされているだけで、同時にテストされていません。Verifyのオーバーロードに相当するものはありませTimesFunc<Times>。メソッドの名前や、そもそもこれが一般的に悪い考えである理由には、おそらくいくつかのより良い名前があります。

それがあなたや誰かに役立つことを願っています!

于 2013-09-29T22:28:41.767 に答える