Skip to content

Commit

Permalink
Take into account base property accessors
Browse files Browse the repository at this point in the history
  • Loading branch information
stakx committed Oct 19, 2019
1 parent 4d64fce commit 6fc4afc
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 4 deletions.
50 changes: 46 additions & 4 deletions src/Moq/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,72 @@ public static bool CanRead(this PropertyInfo property, out MethodInfo getter)
{
if (property.CanRead)
{
// The given `PropertyInfo` should be able to provide a getter:
getter = property.GetGetMethod(nonPublic: true);
Debug.Assert(getter != null);
return true;
}
else
{
getter = null;
return false;
// The given `PropertyInfo` cannot provide a getter... but there may still be one in a base class'
// corresponding `PropertyInfo`! We need to find that base `PropertyInfo`, and because `PropertyInfo`
// does not have `.GetBaseDefinition()`, we'll find it via the setter's `.GetBaseDefinition()`.
// (We may assume that there's a setter because properties/indexers must have at least one accessor.)
Debug.Assert(property.CanWrite);
var setter = property.GetSetMethod(nonPublic: true);
Debug.Assert(setter != null);

var baseSetter = setter.GetBaseDefinition();
if (baseSetter != setter)
{
var baseProperty =
baseSetter
.DeclaringType
.GetMember(property.Name, MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Cast<PropertyInfo>()
.First(p => p.GetSetMethod(nonPublic: true) == baseSetter);
return baseProperty.CanRead(out getter);
}
}

getter = null;
return false;
}

public static bool CanWrite(this PropertyInfo property, out MethodInfo setter)
{
if (property.CanWrite)
{
// The given `PropertyInfo` should be able to provide a setter:
setter = property.GetSetMethod(nonPublic: true);
Debug.Assert(setter != null);
return true;
}
else
{
setter = null;
return false;
// The given `PropertyInfo` cannot provide a setter... but there may still be one in a base class'
// corresponding `PropertyInfo`! We need to find that base `PropertyInfo`, and because `PropertyInfo`
// does not have `.GetBaseDefinition()`, we'll find it via the getter's `.GetBaseDefinition()`.
// (We may assume that there's a getter because properties/indexers must have at least one accessor.)
Debug.Assert(property.CanRead);
var getter = property.GetGetMethod(nonPublic: true);
Debug.Assert(getter != null);

var baseGetter = getter.GetBaseDefinition();
if (baseGetter != getter)
{
var baseProperty =
baseGetter
.DeclaringType
.GetMember(property.Name, MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Cast<PropertyInfo>()
.First(p => p.GetGetMethod(nonPublic: true) == baseGetter);
return baseProperty.CanWrite(out setter);
}
}

setter = null;
return false;
}

/// <summary>
Expand Down
75 changes: 75 additions & 0 deletions tests/Moq.Tests/ExtensionsFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,56 @@ private string GetParameterTypeList(string methodName)
return method.GetParameterTypeList();
}

[Fact]
public void CanRead_returns_false_for_true_write_only_property()
{
var property = typeof(WithWriteOnlyProperty).GetProperty("Property");
Assert.False(property.CanRead(out var getter));
Assert.Null(getter);
}

[Fact]
public void CanRead_identifies_getter_in_true_read_only_property()
{
var property = typeof(WithReadOnlyProperty).GetProperty("Property");
Assert.True(property.CanRead(out var getter));
Assert.Equal(typeof(WithReadOnlyProperty), getter.DeclaringType);
}

[Fact]
public void CanRead_identifies_getter_when_declared_in_base_class()
{
var property = typeof(OverridesOnlySetter).GetProperty("Property");
Assert.False(property.CanRead);
Assert.True(property.CanRead(out var getter));
Assert.Equal(typeof(WithAutoProperty), getter.DeclaringType);
}

[Fact]
public void CanWrite_returns_false_for_true_read_only_property()
{
var property = typeof(WithReadOnlyProperty).GetProperty("Property");
Assert.False(property.CanWrite(out var setter));
Assert.Null(setter);
}

[Fact]
public void CanWrite_identifies_setter_in_true_write_only_property()
{
var property = typeof(WithWriteOnlyProperty).GetProperty("Property");
Assert.True(property.CanWrite(out var setter));
Assert.Equal(typeof(WithWriteOnlyProperty), setter.DeclaringType);
}

[Fact]
public void CanWrite_identifies_setter_when_declared_in_base_class()
{
var property = typeof(OverridesOnlyGetter).GetProperty("Property");
Assert.False(property.CanWrite);
Assert.True(property.CanWrite(out var setter));
Assert.Equal(typeof(WithAutoProperty), setter.DeclaringType);
}

public interface IMethods
{
void Empty();
Expand All @@ -128,6 +178,31 @@ public interface IMethods
void OutInt(out int arg1);
void BoolAndParamsString(bool arg1, params string[] arg2);
}

public class WithReadOnlyProperty
{
public virtual object Property => null;
}

public class WithWriteOnlyProperty
{
public virtual object Property { set { } }
}

public class WithAutoProperty
{
public virtual object Property { get; set; }
}

public class OverridesOnlyGetter : WithAutoProperty
{
public override object Property { get => base.Property; }
}

public class OverridesOnlySetter : WithAutoProperty
{
public override object Property { set => base.Property = value; }
}
}

public interface IFooReset
Expand Down

0 comments on commit 6fc4afc

Please sign in to comment.