Skip to content

Commit

Permalink
code review and fix devlooped#1162
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyhallett committed Jun 23, 2021
1 parent c7df19e commit 2f6a762
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 69 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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)
* 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
}
}
else
{
return node;
return base.VisitMethodCall(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)
Expand All @@ -255,7 +272,7 @@ protected override Expression VisitMember(MemberExpression node)
}
else
{
return node;
return base.VisitMember(node);
}
}

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)
.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
Original file line number Diff line number Diff line change
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));
}

[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()
{
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);
}

[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());

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

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

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

public interface Fooish
{
int VirtualGet { get; set; }
int VirtualSet { get; set; }
int ReadOnlyPropertyImpl { get; }
int ReadWritePropertyImpl { get; set; }
int NonExistentProperty { get; }
Expand Down

0 comments on commit 2f6a762

Please sign in to comment.