Skip to content

Commit

Permalink
Handle protected and internal property setters
Browse files Browse the repository at this point in the history
  • Loading branch information
zvirja committed Jul 14, 2020
1 parent e0cd552 commit f088fa3
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/NSubstitute/Core/PropertyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public bool IsCallToSetAReadWriteProperty(ICall call)

private bool PropertySetterExistsAndHasAGetMethod(PropertyInfo propertySetter)
{
return propertySetter != null && propertySetter.GetGetMethod() != null;
return propertySetter != null && propertySetter.GetGetMethod(nonPublic: true) != null;
}

private PropertyInfo GetPropertyFromSetterCallOrNull(ICall call)
Expand All @@ -41,7 +41,7 @@ public ICall CreateCallToPropertyGetterFromSetterCall(ICall callToSetter)
throw new InvalidOperationException("Could not find a GetMethod for \"" + callToSetter.GetMethodInfo() + "\"");
}

var getter = propertyInfo.GetGetMethod();
var getter = propertyInfo.GetGetMethod(nonPublic: true);
var getterArgs = SkipLast(callToSetter.GetOriginalArguments());
var getterArgumentSpecifications = GetGetterCallSpecificationsFromSetterCall(callToSetter);

Expand Down
17 changes: 10 additions & 7 deletions src/NSubstitute/Core/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,24 @@ public static PropertyInfo GetPropertyFromSetterCallOrNull(this MethodInfo call)
{
if (!CanBePropertySetterCall(call)) return null;

var properties = call.DeclaringType.GetProperties();

// Don't use .FirstOrDefault() lambda, as closure leads to allocation even if not reached.
foreach (var property in properties)
foreach (var property in GetAllProperties(call.DeclaringType))
{
if (property.GetSetMethod() == call) return property;
if (property.GetSetMethod(nonPublic: true) == call) return property;
}

return null;
}

public static PropertyInfo GetPropertyFromGetterCallOrNull(this MethodInfo call)
{
var properties = call.DeclaringType.GetProperties();
return properties.FirstOrDefault(x => x.GetGetMethod() == call);
return GetAllProperties(call.DeclaringType)
.FirstOrDefault(x => x.GetGetMethod(nonPublic: true) == call);
}

public static bool IsParams(this ParameterInfo parameterInfo)
{
return parameterInfo.IsDefined(typeof(ParamArrayAttribute), false);
return parameterInfo.IsDefined(typeof(ParamArrayAttribute), inherit: false);
}

private static bool CanBePropertySetterCall(MethodInfo call)
Expand All @@ -42,5 +40,10 @@ private static bool CanBePropertySetterCall(MethodInfo call)
// misbehaves in those cases. We use slightly slower, but robust check.
return call.Name.StartsWith("set_", StringComparison.Ordinal);
}

private static PropertyInfo[] GetAllProperties(Type type)
{
return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using NUnit.Framework;

namespace NSubstitute.Acceptance.Specs.FieldReports
{
/// <summary>
/// Scenarios for the issue: https://github.com/nsubstitute/NSubstitute/issues/626
///
/// Do not test internal members, as we don't want to set InternalsVisibleTo attribute.
/// </summary>
public class Issue262_NonPublicSetterCall
{
[Test]
public void ShouldHandleProtectedProperties()
{
var subs = Substitute.For<TestClass>();

subs.SetProtectedProp(42);

var result = subs.GetProtectedProp();
Assert.That(result, Is.EqualTo(expected: 42));
}

[Test]
public void ShouldHandlePropertyWithProtectedSetter()
{
var subs = Substitute.For<TestClass>();

subs.SetProtectedSetterProp(42);

var result = subs.ProtectedSetterProp;
Assert.That(result, Is.EqualTo(expected: 42));
}
[Test]
public void ShouldHandlePropertyWithProtectedGetter()
{
var subs = Substitute.For<TestClass>();

subs.ProtectedGetterProp = 42;

var result = subs.GetProtectedGetterProp();
Assert.That(result, Is.EqualTo(expected: 42));
}

public abstract class TestClass
{
protected abstract int ProtectedProp { get; set; }
public void SetProtectedProp(int value) => ProtectedProp = value;
public int GetProtectedProp() => ProtectedProp;

public abstract int ProtectedSetterProp { get; protected set; }
public void SetProtectedSetterProp(int value) => ProtectedSetterProp = value;

public abstract int ProtectedGetterProp { protected get; set; }
public int GetProtectedGetterProp() => ProtectedGetterProp;
}
}
}

0 comments on commit f088fa3

Please sign in to comment.