Skip to content

Commit

Permalink
Reimplement machinery surrounding SetupSequence
Browse files Browse the repository at this point in the history
  • Loading branch information
stakx committed Oct 21, 2017
1 parent 4fa402e commit 6281f4e
Show file tree
Hide file tree
Showing 8 changed files with 351 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,69 +39,67 @@
// http://www.opensource.org/licenses/bsd-license.php]

using System;
using System.Linq.Expressions;
using Moq.Language;
using Moq.Language.Flow;

namespace Moq
namespace Moq.Language.Flow
{
internal sealed class SetupSequentialActionContext<TMock> : ISetupSequentialAction
where TMock : class
// keeping the fluent API separate from `SequenceMethodCall` saves us from having to
// define a generic variant `SequenceMethodCallReturn<TResult>`, which would be much more
// work that having a generic fluent API counterpart `SequenceMethodCall<TResult>`.
internal sealed class SetupSequencePhrase : ISetupSequentialAction
{
private int currentStep;
private int expectationsCount;
private Mock<TMock> mock;
private Expression<Action<TMock>> expression;
private readonly Action callbackAction;

public SetupSequentialActionContext(
Mock<TMock> mock,
Expression<Action<TMock>> expression)
private SequenceMethodCall setup;

public SetupSequencePhrase(SequenceMethodCall setup)
{
this.mock = mock;
this.expression = expression;
this.callbackAction = () => currentStep++;
this.setup = setup;
}

public ISetupSequentialAction Pass()
{
var setup = this.GetSetup();
setup.Callback(DoNothing);
this.EndSetup(setup);
this.setup.AddPass();
return this;
}

private static void DoNothing() { }
public ISetupSequentialAction Throws<TException>()
where TException : Exception, new()
=> this.Throws(new TException());

public ISetupSequentialAction Throws<TException>() where TException : Exception, new()
public ISetupSequentialAction Throws(Exception exception)
{
var setup = this.GetSetup();
setup.Throws<TException>();
this.EndSetup(setup);
this.setup.AddThrows(exception);
return this;
}
}

public ISetupSequentialAction Throws(Exception exception)
internal sealed class SetupSequencePhrase<TResult> : ISetupSequentialResult<TResult>
{
private SequenceMethodCall setup;

public SetupSequencePhrase(SequenceMethodCall setup)
{
var setup = this.GetSetup();
setup.Throws(exception);
this.EndSetup(setup);
return this;
this.setup = setup;
}

private void EndSetup(ICallback callback)
public ISetupSequentialResult<TResult> CallBase()
{
callback.Callback(callbackAction);
this.setup.AddCallBase();
return this;
}

private ISetup<TMock> GetSetup()
public ISetupSequentialResult<TResult> Returns(TResult value)
{
var expectationStep = this.expectationsCount;
this.expectationsCount++;
this.setup.AddReturns(value);
return this;
}

return this.mock
.When(() => currentStep == expectationStep)
.Setup(expression);
public ISetupSequentialResult<TResult> Throws(Exception exception)
{
this.setup.AddThrows(exception);
return this;
}

public ISetupSequentialResult<TResult> Throws<TException>()
where TException : Exception, new()
=> this.Throws(new TException());
}
}
18 changes: 18 additions & 0 deletions Source/Mock.Generic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,24 @@ public Mock<T> SetupAllProperties()
return this;
}

/// <summary>
/// Return a sequence of values, once per call.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By Design")]
public ISetupSequentialResult<TResult> SetupSequence<TResult>(Expression<Func<T, TResult>> expression)
{
return Mock.SetupSequence<TResult>(this, expression);
}

/// <summary>
/// Performs a sequence of actions, one per call.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By Design")]
public ISetupSequentialAction SetupSequence(Expression<Action<T>> expression)
{
return Mock.SetupSequence(this, expression);
}

#endregion

#region When
Expand Down
43 changes: 40 additions & 3 deletions Source/Mock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@
//[This is the BSD license, see
// http://www.opensource.org/licenses/bsd-license.php]

using System.Collections.Concurrent;
using Moq.Properties;
using Moq.Proxy;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
Expand All @@ -50,6 +48,10 @@
using System.Reflection;
using System.Runtime.CompilerServices;

using Moq.Language.Flow;
using Moq.Properties;
using Moq.Proxy;

namespace Moq
{
/// <include file='Mock.xdoc' path='docs/doc[@for="Mock"]/*'/>
Expand Down Expand Up @@ -667,6 +669,41 @@ private static Expression GetValueExpression(object value, Type type)
return Expression.Convert(Expression.Constant(value), type);
}

internal static SetupSequencePhrase<TResult> SetupSequence<TResult>(Mock mock, LambdaExpression expression)
{
if (expression.IsProperty())
{
var prop = expression.ToPropertyInfo();
ThrowIfPropertyNotReadable(prop);

var propGet = prop.GetGetMethod(true);
ThrowIfSetupExpressionInvolvesUnsupportedMember(expression, propGet);
ThrowIfSetupMethodNotVisibleToProxyFactory(propGet);

var setup = new SequenceMethodCall(mock, expression, propGet, new Expression[0]);
var targetInterceptor = GetInterceptor(((MemberExpression)expression.Body).Expression, mock);
targetInterceptor.AddCall(setup, SetupKind.Other);
return new SetupSequencePhrase<TResult>(setup);
}
else
{
var methodCall = expression.GetCallInfo(mock);
var targetInterceptor = GetInterceptor(methodCall.Object, mock);
var setup = new SequenceMethodCall(mock, expression, methodCall.Method, methodCall.Arguments.ToArray());
targetInterceptor.AddCall(setup, SetupKind.Other);
return new SetupSequencePhrase<TResult>(setup);
}
}

internal static SetupSequencePhrase SetupSequence(Mock mock, LambdaExpression expression)
{
var methodCall = expression.GetCallInfo(mock);
var targetInterceptor = GetInterceptor(methodCall.Object, mock);
var setup = new SequenceMethodCall(mock, expression, methodCall.Method, methodCall.Arguments.ToArray());
targetInterceptor.AddCall(setup, SetupKind.Other);
return new SetupSequencePhrase(setup);
}

internal static void SetupAllProperties(Mock mock)
{
PexProtector.Invoke(() =>
Expand Down
78 changes: 78 additions & 0 deletions Source/Obsolete/SequenceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD
//https://github.com/moq/moq4
//All rights reserved.

//Redistribution and use in source and binary forms,
//with or without modification, are permitted provided
//that the following conditions are met:

// * Redistributions of source code must retain the
// above copyright notice, this list of conditions and
// the following disclaimer.

// * Redistributions in binary form must reproduce
// the above copyright notice, this list of conditions
// and the following disclaimer in the documentation
// and/or other materials provided with the distribution.

// * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the
// names of its contributors may be used to endorse
// or promote products derived from this software
// without specific prior written permission.

//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
//SUCH DAMAGE.

//[This is the BSD license, see
// http://www.opensource.org/licenses/bsd-license.php]

using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;

using Moq.Language;

namespace Moq
{
partial class SequenceExtensions
{
/// <summary>
/// Return a sequence of values, once per call.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use instance method Mock<T>.SetupSequence instead.")]
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By Design")]
public static ISetupSequentialResult<TResult> SetupSequence<TMock, TResult>(
this Mock<TMock> mock,
Expression<Func<TMock, TResult>> expression)
where TMock : class
{
return mock.SetupSequence(expression);
}

/// <summary>
/// Performs a sequence of actions, one per call.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use instance method Mock<T>.SetupSequence instead.")]
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By Design")]
public static ISetupSequentialAction SetupSequence<TMock>(this Mock<TMock> mock, Expression<Action<TMock>> expression)
where TMock : class
{
return mock.SetupSequence(expression);
}
}
}
6 changes: 3 additions & 3 deletions Source/Protected/ProtectedMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public ISetupSequentialAction SetupSequence(string methodOrPropertyName, bool ex
ThrowIfMemberMissing(methodOrPropertyName, method);
ThrowIfPublicMethod(method, typeof(T).Name);

return new SetupSequentialActionContext<T>(mock, GetMethodCall(method, args));
return Mock.SetupSequence(mock, GetMethodCall(method, args));
}

public ISetupSequentialResult<TResult> SetupSequence<TResult>(string methodOrPropertyName, params object[] args)
Expand All @@ -155,15 +155,15 @@ public ISetupSequentialResult<TResult> SetupSequence<TResult>(string methodOrPro
{
ThrowIfPublicGetter(property, typeof(T).Name);
// TODO should consider property indexers
return new SetupSequentialContext<T, TResult>(mock, GetMemberAccess<TResult>(property));
return Mock.SetupSequence<TResult>(mock, GetMemberAccess<TResult>(property));
}

var method = GetMethod(methodOrPropertyName, exactParameterMatch, args);
ThrowIfMemberMissing(methodOrPropertyName, method);
ThrowIfVoidMethod(method);
ThrowIfPublicMethod(method, typeof(T).Name);

return new SetupSequentialContext<T, TResult>(mock, GetMethodCall<TResult>(method, args));
return Mock.SetupSequence<TResult>(mock, GetMethodCall<TResult>(method, args));
}

#endregion
Expand Down
72 changes: 44 additions & 28 deletions Source/SequenceExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,55 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
//Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD
//https://github.com/moq/moq4
//All rights reserved.

//Redistribution and use in source and binary forms,
//with or without modification, are permitted provided
//that the following conditions are met:

// * Redistributions of source code must retain the
// above copyright notice, this list of conditions and
// the following disclaimer.

// * Redistributions in binary form must reproduce
// the above copyright notice, this list of conditions
// and the following disclaimer in the documentation
// and/or other materials provided with the distribution.

// * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the
// names of its contributors may be used to endorse
// or promote products derived from this software
// without specific prior written permission.

//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
//CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
//MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
//SUCH DAMAGE.

//[This is the BSD license, see
// http://www.opensource.org/licenses/bsd-license.php]

using System;
using System.Threading.Tasks;

using Moq.Language;

namespace Moq
{
/// <summary>
/// Helper for sequencing return values in the same method.
/// </summary>
public static class SequenceExtensions
public static partial class SequenceExtensions
{
/// <summary>
/// Return a sequence of values, once per call.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By Design")]
public static ISetupSequentialResult<TResult> SetupSequence<TMock, TResult>(
this Mock<TMock> mock,
Expression<Func<TMock, TResult>> expression)
where TMock : class
{
return new SetupSequentialContext<TMock, TResult>(mock, expression);
}

/// <summary>
/// Performs a sequence of actions, one per call.
/// </summary>
public static ISetupSequentialAction SetupSequence<TMock>(
this Mock<TMock> mock,
Expression<Action<TMock>> expression)
where TMock : class
{
return new SetupSequentialActionContext<TMock>(mock, expression);
}

/// <summary>
/// Return a sequence of tasks, once per call.
/// </summary>
Expand All @@ -56,4 +72,4 @@ public static ISetupSequentialResult<Task<TResult>> ThrowsAsync<TResult>(this IS
return setup.Returns(tcs.Task);
}
}
}
}

0 comments on commit 6281f4e

Please sign in to comment.