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 1 commit
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
52 changes: 0 additions & 52 deletions src/Moq/Protected/DuckSetterReplacer.cs

This file was deleted.

9 changes: 6 additions & 3 deletions src/Moq/Protected/IProtectedAsMock.cs
Expand Up @@ -54,7 +54,7 @@ public interface IProtectedAsMock<T, TAnalog> : IFluentInterface
/// </remarks>
/// <example group="setups">
/// <code>
/// mock.SetupSet(x => x.Suspended = true);
/// mock.SetupSet&lt;bool&gt;(x => x.Suspended = true);
/// </code>
/// </example>
ISetupSetter<T, TProperty> SetupSet<TProperty>(Action<TAnalog> setterExpression);
Expand Down Expand Up @@ -132,10 +132,13 @@ public interface IProtectedAsMock<T, TAnalog> : IFluentInterface
void Verify<TResult>(Expression<Func<TAnalog, TResult>> expression, Times? times = null, string failMessage = null);

/// <summary>
/// Verifies that a property was set on the mock, specifying a failure message.
/// Verifies that a property was set on the mock.
/// </summary>
/// <param name="times">The number of times a method is expected to be called. Defaults to Times.AtLeastOnce</param>
/// <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"/>.
Expand Down
42 changes: 29 additions & 13 deletions src/Moq/Protected/ProtectedAsMock.cs
Expand Up @@ -20,7 +20,6 @@ internal sealed class ProtectedAsMock<T, TAnalog> : IProtectedAsMock<T, TAnalog>
private Mock<T> mock;

private static DuckReplacer DuckReplacerInstance = new DuckReplacer(typeof(TAnalog), typeof(T));
private static DuckSetterReplacer<T, TAnalog> DuckSetterReplacerInstance = new DuckSetterReplacer<T, TAnalog>();

public ProtectedAsMock(Mock<T> mock)
{
Expand Down Expand Up @@ -67,13 +66,19 @@ public ISetup<T> Setup(Expression<Action<TAnalog>> expression)

public ISetupSetter<T, TProperty> SetupSet<TProperty>(Action<TAnalog> setterExpression)
{
var setup = Mock.SetupSet(mock, ReplaceDuckSetter(setterExpression), condition: null);
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)
{
var setup = Mock.SetupSet(mock, ReplaceDuckSetter(setterExpression), condition: null);
Guard.NotNull(setterExpression, nameof(setterExpression));

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

Expand Down Expand Up @@ -184,7 +189,10 @@ public void Verify<TResult>(Expression<Func<TAnalog, TResult>> expression, Times

public void VerifySet(Action<TAnalog> setterExpression, Times? times = null, string failMessage = null)
{
Mock.VerifySet(mock, ReplaceDuckSetter(setterExpression), times.HasValue ? times.Value : Times.AtLeastOnce(), failMessage);
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)
Expand All @@ -204,13 +212,12 @@ public void VerifyGet<TProperty>(Expression<Func<TAnalog, TProperty>> expression
Mock.VerifyGet(this.mock, rewrittenExpression, times ?? Times.AtLeastOnce(), failMessage);
}

private Expression<Action<T>> ReplaceDuckSetter(Action<TAnalog> setterExpression)
private LambdaExpression ReconstructAndReplaceSetter(Action<TAnalog> setterExpression)
{
Guard.NotNull(setterExpression, nameof(setterExpression));

var expression = ExpressionReconstructor.Instance.ReconstructExpression(setterExpression, mock.ConstructorArguments);
return DuckSetterReplacerInstance.Replace(expression);
return ReplaceDuck(expression);
}

private static LambdaExpression ReplaceDuck(LambdaExpression expression)
{
Debug.Assert(expression.Parameters.Count == 1);
Expand Down Expand Up @@ -239,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 @@ -255,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 @@ -305,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 Expand Up @@ -388,6 +405,5 @@ private static bool IsCorrespondingProperty(PropertyInfo duckProperty, PropertyI
// TODO: parameter lists should be compared, too, to properly support indexers.
}
}

}
}
82 changes: 81 additions & 1 deletion tests/Moq.Tests/ProtectedAsMockFixture.cs
Expand Up @@ -130,6 +130,13 @@ public void Setup_can_setup_generic_method()
Assert.Equal(3, handledMessages.Count);
}

[Fact]
public void Setup_can_automock()
{
this.protectedMock.Setup(m => m.Nested.Method(1)).Returns(123);
Assert.Equal(123, mock.Object.GetNested().Method(1));
}

stakx marked this conversation as resolved.
Show resolved Hide resolved
[Fact]
public void SetupGet_can_setup_readonly_property()
{
Expand All @@ -150,6 +157,26 @@ public void SetupGet_can_setup_readwrite_property()
Assert.Equal(42, actual);
}

[Fact]
public void SetUpGet_can_automock()
stakx marked this conversation as resolved.
Show resolved Hide resolved
{
this.protectedMock.SetupGet(m => m.Nested.Value).Returns(42);

var actual = mock.Object.GetNested().Value;

Assert.Equal(42, actual);
}

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

var actual = mock.Object.GetVirtual();

Assert.Equal(42, actual);
}

stakx marked this conversation as resolved.
Show resolved Hide resolved
[Fact]
public void SetupProperty_can_setup_readwrite_property()
{
Expand Down Expand Up @@ -259,7 +286,15 @@ public void SetUpSet_Should_Work_With_Indexers()
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());

stakx marked this conversation as resolved.
Show resolved Hide resolved
mock.Object.SetVirtual(123);
Assert.Throws<ExpectedException>(() => mock.Object.SetVirtual(999));
}

[Fact]
Expand Down Expand Up @@ -293,7 +328,6 @@ void VerifySet(Times? times = null,string failMessage = null)

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

}

[Fact]
Expand Down Expand Up @@ -384,12 +418,51 @@ public void VerifyGet_includes_failure_message_in_exception()
public interface INested
{
int Value { get; set; }
int Method(int value);
stakx marked this conversation as resolved.
Show resolved Hide resolved
}

public abstract class Foo
{
protected Foo()
{
}
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 _virtualGet;
}
set
{
_virtualGet = value;
}

}

public int GetVirtual()
{
return VirtualGet;
}
stakx marked this conversation as resolved.
Show resolved Hide resolved

public int ReadOnlyProperty => this.ReadOnlyPropertyImpl;

Expand Down Expand Up @@ -437,10 +510,17 @@ public void SetMultipleIndexer(int index, string sIndex, int value)
{
this[index, sIndex] = value;
}

public int GetMultipleIndexer(int index, string sIndex)
{
return this[index, sIndex];
}
stakx marked this conversation as resolved.
Show resolved Hide resolved
}

public interface Fooish
{
int VirtualGet { get; set; }
stakx marked this conversation as resolved.
Show resolved Hide resolved
int VirtualSet { get; set; }
int ReadOnlyPropertyImpl { get; }
int ReadWritePropertyImpl { get; set; }
int NonExistentProperty { get; }
Expand Down