Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "set" methods to ProtectedAsMock #1165

Merged
merged 7 commits into from Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,8 +7,13 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1

## Unreleased

#### Added

* `SetupSet`, `VerifySet` methods for `mock.Protected().As<>()` (@tonyhallett, #1165)

#### Fixed

* Virtual properties and automocking not working for `mock.Protected().As<>()` (@tonyhallett, #1165)
stakx marked this conversation as resolved.
Show resolved Hide resolved
* Issue mocking VB.NET class with overloaded property/indexer in base class (@myurashchyk, #1153)


Expand Down
2 changes: 1 addition & 1 deletion src/Moq/Mock`1.cs
Expand Up @@ -480,7 +480,7 @@ public ISetup<T> Setup(Expression<Action<T>> expression)
/// Specifies a setup on the mocked type for a call to a property setter.
/// </summary>
/// <param name="setterExpression">The Lambda expression that sets a property to a value.</param>
/// <typeparam name="TProperty">Type of the property. Typically omitted as it can be inferred from the expression.</typeparam>
/// <typeparam name="TProperty">Type of the property.</typeparam>
/// <remarks>
/// If more than one setup is set for the same property setter,
/// the latest one wins and is the one that will be executed.
Expand Down
49 changes: 49 additions & 0 deletions src/Moq/Protected/IProtectedAsMock.cs
Expand Up @@ -39,6 +39,41 @@ public interface IProtectedAsMock<T, TAnalog> : IFluentInterface
/// <seealso cref="Mock{T}.Setup{TResult}(Expression{Func{T, TResult}})"/>
ISetup<T, TResult> Setup<TResult>(Expression<Func<TAnalog, TResult>> expression);


/// <summary>
/// Specifies a setup on the mocked type for a call to a property setter.
/// </summary>
/// <param name="setterExpression">The Lambda expression that sets a property to a value.</param>
/// <typeparam name="TProperty">Type of the property.</typeparam>
/// <remarks>
/// If more than one setup is set for the same property setter,
/// the latest one wins and is the one that will be executed.
/// <para>
/// This overloads allows the use of a callback already typed for the property type.
/// </para>
/// </remarks>
/// <example group="setups">
/// <code>
/// mock.SetupSet&lt;bool&gt;(x => x.Suspended = true);
/// </code>
/// </example>
ISetupSetter<T, TProperty> SetupSet<TProperty>(Action<TAnalog> setterExpression);

/// <summary>
/// Specifies a setup on the mocked type for a call to a property setter.
/// </summary>
/// <param name="setterExpression">Lambda expression that sets a property to a value.</param>
/// <remarks>
/// If more than one setup is set for the same property setter,
/// the latest one wins and is the one that will be executed.
/// </remarks>
/// <example group="setups">
/// <code>
/// mock.SetupSet(x => x.Suspended = true);
/// </code>
/// </example>
ISetup<T> SetupSet(Action<TAnalog> setterExpression);

/// <summary>
/// Specifies a setup on the mocked type for a call to a property getter.
/// </summary>
Expand Down Expand Up @@ -96,6 +131,20 @@ public interface IProtectedAsMock<T, TAnalog> : IFluentInterface
/// <exception cref="MockException">The specified invocation did not occur (or did not occur the specified number of times).</exception>
void Verify<TResult>(Expression<Func<TAnalog, TResult>> expression, Times? times = null, string failMessage = null);

/// <summary>
/// Verifies that a property was set on the mock.
/// </summary>
/// <param name="setterExpression">Expression to verify.</param>
/// <param name="times">
/// Number of times that the setter is expected to have occurred.
/// If omitted, assumed to be <see cref="Times.AtLeastOnce"/>.
/// </param>
/// <param name="failMessage">Message to show if verification fails.</param>
/// <exception cref="MockException">
/// The invocation was not called the number of times specified by <paramref name="times"/>.
/// </exception>
void VerifySet(Action<TAnalog> setterExpression, Times? times = null, string failMessage = null);

/// <summary>
/// Verifies that a property was read on the mock.
/// </summary>
Expand Down
50 changes: 46 additions & 4 deletions src/Moq/Protected/ProtectedAsMock.cs
Expand Up @@ -64,6 +64,24 @@ public ISetup<T> Setup(Expression<Action<TAnalog>> expression)
return new NonVoidSetupPhrase<T, TResult>(setup);
}

public ISetupSetter<T, TProperty> SetupSet<TProperty>(Action<TAnalog> setterExpression)
{
Guard.NotNull(setterExpression, nameof(setterExpression));

var rewrittenExpression = ReconstructAndReplaceSetter(setterExpression);
var setup = Mock.SetupSet(mock, rewrittenExpression, condition: null);
return new SetterSetupPhrase<T, TProperty>(setup);
}

public ISetup<T> SetupSet(Action<TAnalog> setterExpression)
{
Guard.NotNull(setterExpression, nameof(setterExpression));

var rewrittenExpression = ReconstructAndReplaceSetter(setterExpression);
var setup = Mock.SetupSet(mock, rewrittenExpression, condition: null);
return new VoidSetupPhrase<T>(setup);
}

public ISetupGetter<T, TProperty> SetupGet<TProperty>(Expression<Func<TAnalog, TProperty>> expression)
{
Guard.NotNull(expression, nameof(expression));
Expand Down Expand Up @@ -169,6 +187,14 @@ public void Verify<TResult>(Expression<Func<TAnalog, TResult>> expression, Times
Mock.Verify(this.mock, rewrittenExpression, times ?? Times.AtLeastOnce(), failMessage);
}

public void VerifySet(Action<TAnalog> setterExpression, Times? times = null, string failMessage = null)
{
Guard.NotNull(setterExpression, nameof(setterExpression));

var rewrittenExpression = ReconstructAndReplaceSetter(setterExpression);
Mock.VerifySet(mock, rewrittenExpression, times.HasValue ? times.Value : Times.AtLeastOnce(), failMessage);
}

public void VerifyGet<TProperty>(Expression<Func<TAnalog, TProperty>> expression, Times? times = null, string failMessage = null)
{
Guard.NotNull(expression, nameof(expression));
Expand All @@ -186,6 +212,12 @@ public void VerifyGet<TProperty>(Expression<Func<TAnalog, TProperty>> expression
Mock.VerifyGet(this.mock, rewrittenExpression, times ?? Times.AtLeastOnce(), failMessage);
}

private LambdaExpression ReconstructAndReplaceSetter(Action<TAnalog> setterExpression)
{
var expression = ExpressionReconstructor.Instance.ReconstructExpression(setterExpression, mock.ConstructorArguments);
return ReplaceDuck(expression);
}

private static LambdaExpression ReplaceDuck(LambdaExpression expression)
{
Debug.Assert(expression.Parameters.Count == 1);
Expand Down Expand Up @@ -214,11 +246,21 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
{
var targetParameter = Expression.Parameter(this.targetType, left.Name);
return Expression.Call(targetParameter, FindCorrespondingMethod(node.Method), node.Arguments);
}
}
stakx marked this conversation as resolved.
Show resolved Hide resolved
else
{
return node;
return base.VisitMethodCall(node);
stakx marked this conversation as resolved.
Show resolved Hide resolved
}
}

protected override Expression VisitIndex(IndexExpression node)
{
if (node.Object is ParameterExpression left && left.Type == this.duckType)
{
var targetParameter = Expression.Parameter(this.targetType, left.Name);
return Expression.MakeIndex(targetParameter, FindCorrespondingProperty(node.Indexer), node.Arguments);
}
return base.VisitIndex(node);
}

protected override Expression VisitMember(MemberExpression node)
Expand All @@ -230,7 +272,7 @@ protected override Expression VisitMember(MemberExpression node)
}
else
{
return node;
return base.VisitMember(node);
stakx marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -280,7 +322,7 @@ private PropertyInfo FindCorrespondingProperty(PropertyInfo duckProperty)
{
var candidateTargetProperties =
this.targetType
.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
stakx marked this conversation as resolved.
Show resolved Hide resolved
.Where(ctp => IsCorrespondingProperty(duckProperty, ctp))
.ToArray();

Expand Down