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 all 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: 4 additions & 1 deletion CHANGELOG.md
Expand Up @@ -7,10 +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, #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));
}

stakx marked this conversation as resolved.
Show resolved Hide resolved
[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);
stakx marked this conversation as resolved.
Show resolved Hide resolved
}

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;
}
}
}