Skip to content

Commit

Permalink
Merge pull request #952 from stakx/split-properties
Browse files Browse the repository at this point in the history
Fix `SetupProperty` for partially overridden properties
  • Loading branch information
stakx authored Oct 19, 2019
2 parents a854f34 + 7567145 commit f0968cc
Show file tree
Hide file tree
Showing 9 changed files with 25 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
* `ArgumentException` ("Interface not found") when setting up `object.ToString` on an interface mock (@vslynko, #942)
* Cannot "return" to original mocked type after downcasting with `Mock.Get` and then upcasting with `mock.As<>` (@pjquirk, #943)
* `params` arrays in recursive setup expressions are matched by reference equality instead of by structural equality (@danielcweber, #946)
* `mock.SetupProperty` throws `NullReferenceException` when called for partially overridden property (@stakx, #951)

## 4.13.0 (2019-08-31)

Expand Down
8 changes: 5 additions & 3 deletions src/Moq/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,8 @@ void Split(Expression e, out Expression r /* remainder */, out InvocationShape p
r = memberAccessExpression.Expression;
var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
var property = memberAccessExpression.GetReboundProperty();
var method = property.CanRead ? property.GetGetMethod(true) : property.GetSetMethod(true);
// ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
var method = property.CanRead(out var getter) ? getter : property.GetSetMethod(true);
// ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// We're in the switch case block for property read access, therefore we prefer the
// getter. When a read-write property is being assigned to, we end up here, too, and
// select the wrong accessor. However, that doesn't matter because it will be over-
Expand Down Expand Up @@ -324,8 +324,10 @@ internal static PropertyInfo GetReboundProperty(this MemberExpression expression
.GetMember(property.Name, MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Cast<PropertyInfo>()
.SingleOrDefault(p => p.PropertyType == property.PropertyType);
if (derivedProperty != null && derivedProperty.GetMethod.GetBaseDefinition() == property.GetMethod)
if (derivedProperty != null)
{
if ((derivedProperty.CanRead(out var getter) && getter.GetBaseDefinition() == property.GetGetMethod(true)) ||
(derivedProperty.CanWrite(out var setter) && setter.GetBaseDefinition() == property.GetSetMethod(true)))
return derivedProperty;
}
}
Expand Down
16 changes: 2 additions & 14 deletions src/Moq/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,24 +250,12 @@ public static bool CanOverride(this MethodBase method)

public static bool CanOverrideGet(this PropertyInfo property)
{
if (property.CanRead)
{
var getter = property.GetGetMethod(true);
return getter != null && getter.CanOverride();
}

return false;
return property.CanRead(out var getter) && getter.CanOverride();
}

public static bool CanOverrideSet(this PropertyInfo property)
{
if (property.CanWrite)
{
var setter = property.GetSetMethod(true);
return setter != null && setter.CanOverride();
}

return false;
return property.CanWrite(out var setter) && setter.CanOverride();
}

public static IEnumerable<MethodInfo> GetMethods(this Type type, string name)
Expand Down
4 changes: 2 additions & 2 deletions src/Moq/Guard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ public static void Positive(TimeSpan delay)

public static void CanRead(PropertyInfo property)
{
if (!property.CanRead)
if (!property.CanRead(out _))
{
throw new ArgumentException(string.Format(
CultureInfo.CurrentCulture,
Expand All @@ -240,7 +240,7 @@ public static void CanRead(PropertyInfo property)

public static void CanWrite(PropertyInfo property)
{
if (!property.CanWrite)
if (!property.CanWrite(out _))
{
throw new ArgumentException(string.Format(
CultureInfo.CurrentCulture,
Expand Down
2 changes: 1 addition & 1 deletion src/Moq/Linq/MockSetupsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ private static Expression ConvertToSetupProperty(Expression targetObject, Expres
// where Mock.Get(foo).SetupProperty(mock => mock.Name, "bar") != null

// if the property is readonly, we can only do a Setup(...) which is the same as a method setup.
if (!propertyInfo.CanWrite || propertyInfo.GetSetMethod(true).IsPrivate)
if (!propertyInfo.CanWrite(out var setter) || setter.IsPrivate)
return ConvertToSetup(targetObject, left, right);

// This will get up to and including the Mock.Get(foo).Setup(mock => mock.Name) call.
Expand Down
2 changes: 1 addition & 1 deletion src/Moq/Mock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ internal void AddInnerMockSetup(MethodInfo method, IReadOnlyList<Expression> arg
var property = expression.ToPropertyInfo();
Guard.CanRead(property);

Debug.Assert(method == property.GetGetMethod(true));
Debug.Assert(property.CanRead(out var getter) && method == getter);
}

this.Setups.Add(new InnerMockSetup(new InvocationShape(expression, method, arguments, exactGenericTypeArguments: true), returnValue));
Expand Down
4 changes: 2 additions & 2 deletions src/Moq/Protected/ProtectedAsMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ private static bool IsCorrespondingProperty(PropertyInfo duckProperty, PropertyI
{
return candidateTargetProperty.Name == duckProperty.Name
&& candidateTargetProperty.PropertyType == duckProperty.PropertyType
&& candidateTargetProperty.CanRead == duckProperty.CanRead
&& candidateTargetProperty.CanWrite == duckProperty.CanWrite;
&& candidateTargetProperty.CanRead(out _) == duckProperty.CanRead(out _)
&& candidateTargetProperty.CanWrite(out _) == duckProperty.CanWrite(out _);

// TODO: parameter lists should be compared, too, to properly support indexers.
}
Expand Down
4 changes: 2 additions & 2 deletions src/Moq/Protected/ProtectedMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ private static void ThrowIfPublicMethod(MethodInfo method, string reflectedTypeN

private static void ThrowIfPublicGetter(PropertyInfo property, string reflectedTypeName)
{
if (property.CanRead && property.GetGetMethod() != null)
if (property.CanRead(out var getter) && getter.IsPublic)
{
throw new ArgumentException(string.Format(
CultureInfo.CurrentCulture,
Expand All @@ -342,7 +342,7 @@ private static void ThrowIfPublicGetter(PropertyInfo property, string reflectedT

private static void ThrowIfPublicSetter(PropertyInfo property, string reflectedTypeName)
{
if (property.CanWrite && property.GetSetMethod() != null)
if (property.CanWrite(out var setter) && setter.IsPublic)
{
throw new ArgumentException(string.Format(
CultureInfo.CurrentCulture,
Expand Down
9 changes: 9 additions & 0 deletions tests/Moq.Tests/StubExtensionsFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,15 @@ public void SetupAllProperties_retains_value_of_derived_read_write_property_that
Assert.Equal("value", mock.Object.Property);
}

[Fact]
public void SetupProperty_retains_value_of_derived_read_write_property_that_overrides_only_setter()
{
var mock = new Mock<OverridesOnlySetter>();
mock.SetupProperty(m => m.Property);
mock.Object.Property = "value";
Assert.Equal("value", mock.Object.Property);
}

private object GetValue() { return new object(); }

public interface IFoo
Expand Down

0 comments on commit f0968cc

Please sign in to comment.