Skip to content

Commit e66e8b4

Browse files
authored
Handle protected and internal property setters (#627)
1 parent e0cd552 commit e66e8b4

File tree

3 files changed

+69
-9
lines changed

3 files changed

+69
-9
lines changed

src/NSubstitute/Core/PropertyHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public bool IsCallToSetAReadWriteProperty(ICall call)
2525

2626
private bool PropertySetterExistsAndHasAGetMethod(PropertyInfo propertySetter)
2727
{
28-
return propertySetter != null && propertySetter.GetGetMethod() != null;
28+
return propertySetter != null && propertySetter.GetGetMethod(nonPublic: true) != null;
2929
}
3030

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

44-
var getter = propertyInfo.GetGetMethod();
44+
var getter = propertyInfo.GetGetMethod(nonPublic: true);
4545
var getterArgs = SkipLast(callToSetter.GetOriginalArguments());
4646
var getterArgumentSpecifications = GetGetterCallSpecificationsFromSetterCall(callToSetter);
4747

src/NSubstitute/Core/ReflectionExtensions.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,24 @@ public static PropertyInfo GetPropertyFromSetterCallOrNull(this MethodInfo call)
1010
{
1111
if (!CanBePropertySetterCall(call)) return null;
1212

13-
var properties = call.DeclaringType.GetProperties();
14-
1513
// Don't use .FirstOrDefault() lambda, as closure leads to allocation even if not reached.
16-
foreach (var property in properties)
14+
foreach (var property in GetAllProperties(call.DeclaringType))
1715
{
18-
if (property.GetSetMethod() == call) return property;
16+
if (property.GetSetMethod(nonPublic: true) == call) return property;
1917
}
2018

2119
return null;
2220
}
2321

2422
public static PropertyInfo GetPropertyFromGetterCallOrNull(this MethodInfo call)
2523
{
26-
var properties = call.DeclaringType.GetProperties();
27-
return properties.FirstOrDefault(x => x.GetGetMethod() == call);
24+
return GetAllProperties(call.DeclaringType)
25+
.FirstOrDefault(x => x.GetGetMethod(nonPublic: true) == call);
2826
}
2927

3028
public static bool IsParams(this ParameterInfo parameterInfo)
3129
{
32-
return parameterInfo.IsDefined(typeof(ParamArrayAttribute), false);
30+
return parameterInfo.IsDefined(typeof(ParamArrayAttribute), inherit: false);
3331
}
3432

3533
private static bool CanBePropertySetterCall(MethodInfo call)
@@ -42,5 +40,10 @@ private static bool CanBePropertySetterCall(MethodInfo call)
4240
// misbehaves in those cases. We use slightly slower, but robust check.
4341
return call.Name.StartsWith("set_", StringComparison.Ordinal);
4442
}
43+
44+
private static PropertyInfo[] GetAllProperties(Type type)
45+
{
46+
return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
47+
}
4548
}
4649
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using NUnit.Framework;
2+
3+
namespace NSubstitute.Acceptance.Specs.FieldReports
4+
{
5+
/// <summary>
6+
/// Scenarios for the issue: https://github.com/nsubstitute/NSubstitute/issues/626
7+
///
8+
/// Do not test internal members, as we don't want to set InternalsVisibleTo attribute.
9+
/// </summary>
10+
public class Issue262_NonPublicSetterCall
11+
{
12+
[Test]
13+
public void ShouldHandleProtectedProperties()
14+
{
15+
var subs = Substitute.For<TestClass>();
16+
17+
subs.SetProtectedProp(42);
18+
19+
var result = subs.GetProtectedProp();
20+
Assert.That(result, Is.EqualTo(expected: 42));
21+
}
22+
23+
[Test]
24+
public void ShouldHandlePropertyWithProtectedSetter()
25+
{
26+
var subs = Substitute.For<TestClass>();
27+
28+
subs.SetProtectedSetterProp(42);
29+
30+
var result = subs.ProtectedSetterProp;
31+
Assert.That(result, Is.EqualTo(expected: 42));
32+
}
33+
[Test]
34+
public void ShouldHandlePropertyWithProtectedGetter()
35+
{
36+
var subs = Substitute.For<TestClass>();
37+
38+
subs.ProtectedGetterProp = 42;
39+
40+
var result = subs.GetProtectedGetterProp();
41+
Assert.That(result, Is.EqualTo(expected: 42));
42+
}
43+
44+
public abstract class TestClass
45+
{
46+
protected abstract int ProtectedProp { get; set; }
47+
public void SetProtectedProp(int value) => ProtectedProp = value;
48+
public int GetProtectedProp() => ProtectedProp;
49+
50+
public abstract int ProtectedSetterProp { get; protected set; }
51+
public void SetProtectedSetterProp(int value) => ProtectedSetterProp = value;
52+
53+
public abstract int ProtectedGetterProp { protected get; set; }
54+
public int GetProtectedGetterProp() => ProtectedGetterProp;
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)