Skip to content

Commit

Permalink
Add "set" methods to ProtectedAsMock (#1165)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyhallett committed Jul 22, 2021
1 parent 8fa13f4 commit 9e148f6
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 11 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Expand Up @@ -7,12 +7,15 @@ 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

* Parameter is invalid in Protected().SetupSet() ... VerifySet (@tonyhallett, #1186)

* Virtual properties and automocking not working for `mock.Protected().As<>()` (@tonyhallett, #1185)

* 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
42 changes: 42 additions & 0 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 @@ -221,6 +253,16 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
}
}

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)
{
if (node.Expression is ParameterExpression left && left.Type == this.duckType)
Expand Down
143 changes: 134 additions & 9 deletions tests/Moq.Tests/ProtectedAsMockFixture.cs
Expand Up @@ -167,9 +167,10 @@ public void SetUpGet_can_automock()
Assert.Equal(42, actual);
}

[Fact] void SetupGet_can_setup_virtual_property()
[Fact]
public void SetupGet_can_setup_virtual_property()
{
this.protectedMock.SetupGet(m => m.Virtual).Returns(42);
this.protectedMock.SetupGet(m => m.VirtualGet).Returns(42);

var actual = mock.Object.GetVirtual();

Expand Down Expand Up @@ -239,6 +240,96 @@ public void SetupSequence_can_setup_actions()
Assert.IsType<InvalidOperationException>(exception);
}

[Fact]
public void SetUpSet_should_setup_setters()
{
this.protectedMock.SetupSet(fish => fish.ReadWritePropertyImpl = 999).Throws(ExpectedException.Instance);

mock.Object.ReadWriteProperty = 123;

Assert.Throws<ExpectedException>(() => mock.Object.ReadWriteProperty = 999);
}

[Fact]
public void SetUpSet_should_setup_setters_with_property_type()
{
int value = 0;
this.protectedMock.SetupSet<int>(fish => fish.ReadWritePropertyImpl = 999).Callback(i => value = i);

mock.Object.ReadWriteProperty = 123;
Assert.Equal(0, value);

mock.Object.ReadWriteProperty = 999;
Assert.Equal(999, value);
}

[Fact]
public void SetUpSet_should_work_recursively()
{
this.protectedMock.SetupSet(f => f.Nested.Value = 999).Throws(ExpectedException.Instance);

mock.Object.GetNested().Value = 1;

Assert.Throws<ExpectedException>(() => mock.Object.GetNested().Value = 999);
}

[Fact]
public void SetUpSet_Should_Work_With_Indexers()
{
this.protectedMock.SetupSet(
o => o[
It.IsInRange(0, 5, Range.Inclusive),
It.IsIn("Bad", "JustAsBad")
] = It.Is<int>(i => i > 10)
).Throws(ExpectedException.Instance);

mock.Object.SetMultipleIndexer(1, "Ok", 999);

Assert.Throws<ExpectedException>(() => mock.Object.SetMultipleIndexer(1, "Bad", 999));
}

[Fact]
public void SetupSet_can_setup_virtual_property()
{
this.protectedMock.SetupSet(m => m.VirtualSet = 999).Throws(new ExpectedException());

mock.Object.SetVirtual(123);
Assert.Throws<ExpectedException>(() => mock.Object.SetVirtual(999));
}

[Fact]
public void VerifySet_Should_Work()
{
void VerifySet(Times? times = null,string failMessage = null)
{
this.protectedMock.VerifySet(
o => o[
It.IsInRange(0, 5, Moq.Range.Inclusive),
It.IsIn("Bad", "JustAsBad")
] = It.Is<int>(i => i > 10),
times,
failMessage
);
}
VerifySet(Times.Never());

mock.Object.SetMultipleIndexer(1, "Ok", 1);
VerifySet(Times.Never());

Assert.Throws<MockException>(() => VerifySet()); // AtLeastOnce

mock.Object.SetMultipleIndexer(1, "Bad", 999);
VerifySet(); // AtLeastOnce

mock.Object.SetMultipleIndexer(1, "JustAsBad", 12);
VerifySet(Times.Exactly(2));

Assert.Throws<MockException>(() => VerifySet(Times.AtMostOnce()));

var mockException = Assert.Throws<MockException>(() => VerifySet(Times.AtMostOnce(),"custom fail message"));
Assert.StartsWith("custom fail message", mockException.Message);
}

[Fact]
public void Verify_can_verify_method_invocations()
{
Expand Down Expand Up @@ -326,7 +417,7 @@ public void VerifyGet_includes_failure_message_in_exception()

public interface INested
{
int Value { get; }
int Value { get; set; }
int Method(int value);
}

Expand Down Expand Up @@ -376,23 +467,49 @@ public INested GetNested()
return Nested;
}

private int virtualProperty;
public virtual int Virtual
protected abstract int this[int i, string s] { get; set; }

public void SetMultipleIndexer(int index, string sIndex, int value)
{
this[index, sIndex] = value;
}

private int _virtualSet;
public virtual int VirtualSet
{
get
{
return _virtualSet;
}
protected set
{
_virtualSet = value;
}

}

public void SetVirtual(int value)
{
VirtualSet = value;
}

private int _virtualGet;
public virtual int VirtualGet
{
protected get
{
return virtualProperty;
return _virtualGet;
}
set
{
virtualProperty = value;
_virtualGet = value;
}

}

public int GetVirtual()
{
return Virtual;
return VirtualGet;
}
}

Expand All @@ -406,7 +523,9 @@ public interface Fooish
int GetSomethingImpl();
void NonExistentMethod();
INested Nested { get; set; }
int Virtual { get; set; }
int this[int i, string s] { get; set; }
int VirtualGet { get; set; }
int VirtualSet { get; set; }
}

public abstract class MessageHandlerBase
Expand All @@ -426,5 +545,11 @@ public interface MessageHandlerBaseish
{
void HandleImpl<TMessage>(TMessage message);
}

public class ExpectedException : Exception
{
private static ExpectedException expectedException = new ExpectedException();
public static ExpectedException Instance => expectedException;
}
}
}

0 comments on commit 9e148f6

Please sign in to comment.