// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Xunit; namespace Microsoft.Extensions.Internal { public class PropertyHelperTest { [Fact] public void PropertyHelper_ReturnsNameCorrectly() { // Arrange var anonymous = new { foo = "bar" }; var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; // Act var helper = new PropertyHelper(property); // Assert Assert.Equal("foo", property.Name); Assert.Equal("foo", helper.Name); } [Fact] public void PropertyHelper_ReturnsValueCorrectly() { // Arrange var anonymous = new { bar = "baz" }; var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; // Act var helper = new PropertyHelper(property); // Assert Assert.Equal("bar", helper.Name); Assert.Equal("baz", helper.GetValue(anonymous)); } [Fact] public void PropertyHelper_ReturnsGetterDelegate() { // Arrange var anonymous = new { bar = "baz" }; var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; // Act var helper = new PropertyHelper(property); // Assert Assert.NotNull(helper.ValueGetter); Assert.Equal("baz", helper.ValueGetter(anonymous)); } [Fact] public void SetValue_SetsPropertyValue() { // Arrange var expected = "new value"; var instance = new BaseClass { PropA = "old value" }; var helper = PropertyHelper.GetProperties( instance.GetType()).First(prop => prop.Name == "PropA"); // Act helper.SetValue(instance, expected); // Assert Assert.Equal(expected, instance.PropA); } [Fact] public void PropertyHelper_ReturnsSetterDelegate() { // Arrange var expected = "new value"; var instance = new BaseClass { PropA = "old value" }; var helper = PropertyHelper.GetProperties( instance.GetType()).First(prop => prop.Name == "PropA"); // Act and Assert Assert.NotNull(helper.ValueSetter); helper.ValueSetter(instance, expected); // Assert Assert.Equal(expected, instance.PropA); } [Fact] public void PropertyHelper_ReturnsValueCorrectly_ForValueTypes() { // Arrange var anonymous = new { foo = 32 }; var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; // Act var helper = new PropertyHelper(property); // Assert Assert.Equal("foo", helper.Name); Assert.Equal(32, helper.GetValue(anonymous)); } [Fact] public void PropertyHelper_ReturnsCachedPropertyHelper() { // Arrange var anonymous = new { foo = "bar" }; // Act var helpers1 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()); var helpers2 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()); // Assert Assert.Single(helpers1); Assert.Same(helpers1, helpers2); Assert.Same(helpers1[0], helpers2[0]); } [Fact] public void PropertyHelper_DoesNotChangeUnderscores() { // Arrange var anonymous = new { bar_baz2 = "foo" }; // Act + Assert var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); Assert.Equal("bar_baz2", helper.Name); } [Fact] public void PropertyHelper_DoesNotFindPrivateProperties() { // Arrange var anonymous = new PrivateProperties(); // Act + Assert var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); Assert.Equal("Prop1", helper.Name); } [Fact] public void PropertyHelper_DoesNotFindStaticProperties() { // Arrange var anonymous = new Static(); // Act + Assert var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); Assert.Equal("Prop5", helper.Name); } #if NETSTANDARD || NETCOREAPP [Fact] public void PropertyHelper_RefStructProperties() { // Arrange var obj = new RefStructProperties(); // Act + Assert var helper = Assert.Single(PropertyHelper.GetProperties(obj.GetType().GetTypeInfo())); Assert.Equal("Prop5", helper.Name); } #elif NET46 || NET461 #else #error Unknown TFM - update the set of TFMs where we test for ref structs #endif [Fact] public void PropertyHelper_DoesNotFindSetOnlyProperties() { // Arrange var anonymous = new SetOnly(); // Act + Assert var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); Assert.Equal("Prop6", helper.Name); } [Theory] [InlineData(typeof(int?))] [InlineData(typeof(DayOfWeek?))] public void PropertyHelper_WorksForNullablePrimitiveAndEnumTypes(Type nullableType) { // Act var properties = PropertyHelper.GetProperties(nullableType); // Assert Assert.Empty(properties); } [Fact] public void PropertyHelper_UnwrapsNullableTypes() { // Arrange var myType = typeof(MyStruct?); // Act var properties = PropertyHelper.GetProperties(myType); // Assert var property = Assert.Single(properties); Assert.Equal("Foo", property.Name); } [Fact] public void PropertyHelper_WorksForStruct() { // Arrange var anonymous = new MyProperties(); anonymous.IntProp = 3; anonymous.StringProp = "Five"; // Act + Assert var helper1 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "IntProp")); var helper2 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "StringProp")); Assert.Equal(3, helper1.GetValue(anonymous)); Assert.Equal("Five", helper2.GetValue(anonymous)); } [Fact] public void PropertyHelper_ForDerivedClass() { // Arrange var derived = new DerivedClass { PropA = "propAValue", PropB = "propBValue" }; // Act var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); // Assert Assert.NotNull(helpers); Assert.Equal(2, helpers.Length); var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); Assert.Equal("propAValue", propAHelper.GetValue(derived)); Assert.Equal("propBValue", propBHelper.GetValue(derived)); } [Fact] public void PropertyHelper_ForDerivedClass_WithNew() { // Arrange var derived = new DerivedClassWithNew { PropA = "propAValue" }; // Act var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); // Assert Assert.NotNull(helpers); Assert.Equal(2, helpers.Length); var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); Assert.Equal("propAValue", propAHelper.GetValue(derived)); Assert.Equal("Newed", propBHelper.GetValue(derived)); } [Fact] public void PropertyHelper_ForDerived_WithVirtual() { // Arrange var derived = new DerivedClassWithOverride { PropA = "propAValue", PropB = "propBValue" }; // Act var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); // Assert Assert.NotNull(helpers); Assert.Equal(2, helpers.Length); var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); Assert.Equal("OverridenpropAValue", propAHelper.GetValue(derived)); Assert.Equal("propBValue", propBHelper.GetValue(derived)); } [Fact] public void PropertyHelper_ForInterface_ReturnsExpectedProperties() { // Arrange var expectedNames = new[] { "Count", "IsReadOnly" }; // Act var helpers = PropertyHelper.GetProperties(typeof(ICollection)); // Assert Assert.Collection( helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal), helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); }, helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); }); } [Fact] public void PropertyHelper_ForDerivedInterface_ReturnsAllProperties() { // Arrange var expectedNames = new[] { "Count", "IsReadOnly", "Keys", "Values" }; // Act var helpers = PropertyHelper.GetProperties(typeof(IDictionary)); // Assert Assert.Collection( helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal), helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); }, helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); }, helper => { Assert.Equal(expectedNames[2], helper.Name, StringComparer.Ordinal); }, helper => { Assert.Equal(expectedNames[3], helper.Name, StringComparer.Ordinal); }); } [Fact] public void GetProperties_ExcludesIndexersAndPropertiesWithoutPublicGetters() { // Arrange var type = typeof(DerivedClassWithNonReadableProperties); // Act var result = PropertyHelper.GetProperties(type).ToArray(); // Assert Assert.Equal(3, result.Length); Assert.Equal("Visible", result[0].Name); Assert.Equal("PropA", result[1].Name); Assert.Equal("PropB", result[2].Name); } [Fact] public void GetVisibleProperties_NoHiddenProperty() { // Arrange var type = typeof(string); // Act var result = PropertyHelper.GetVisibleProperties(type).ToArray(); // Assert var property = Assert.Single(result); Assert.Equal("Length", property.Name); Assert.Equal(typeof(int), property.Property.PropertyType); } [Fact] public void GetVisibleProperties_HiddenProperty() { // Arrange var type = typeof(DerivedHiddenProperty); // Act var result = PropertyHelper.GetVisibleProperties(type).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal("Id", result[0].Name); Assert.Equal(typeof(string), result[0].Property.PropertyType); Assert.Equal("Name", result[1].Name); Assert.Equal(typeof(string), result[1].Property.PropertyType); } [Fact] public void GetVisibleProperties_HiddenProperty_TwoLevels() { // Arrange var type = typeof(DerivedHiddenProperty2); // Act var result = PropertyHelper.GetVisibleProperties(type).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal("Id", result[0].Name); Assert.Equal(typeof(Guid), result[0].Property.PropertyType); Assert.Equal("Name", result[1].Name); Assert.Equal(typeof(string), result[1].Property.PropertyType); } [Fact] public void GetVisibleProperties_NoHiddenPropertyWithTypeInfoInput() { // Arrange var type = typeof(string); // Act var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); // Assert var property = Assert.Single(result); Assert.Equal("Length", property.Name); Assert.Equal(typeof(int), property.Property.PropertyType); } [Fact] public void GetVisibleProperties_HiddenPropertyWithTypeInfoInput() { // Arrange var type = typeof(DerivedHiddenProperty); // Act var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal("Id", result[0].Name); Assert.Equal(typeof(string), result[0].Property.PropertyType); Assert.Equal("Name", result[1].Name); Assert.Equal(typeof(string), result[1].Property.PropertyType); } [Fact] public void GetVisibleProperties_HiddenProperty_TwoLevelsWithTypeInfoInput() { // Arrange var type = typeof(DerivedHiddenProperty2); // Act var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal("Id", result[0].Name); Assert.Equal(typeof(Guid), result[0].Property.PropertyType); Assert.Equal("Name", result[1].Name); Assert.Equal(typeof(string), result[1].Property.PropertyType); } [Fact] public void MakeFastPropertySetter_SetsPropertyValues_ForPublicAndNobPublicProperties() { // Arrange var instance = new BaseClass(); var typeInfo = instance.GetType().GetTypeInfo(); var publicProperty = typeInfo.GetDeclaredProperty("PropA"); var protectedProperty = typeInfo.GetDeclaredProperty("PropProtected"); var publicPropertySetter = PropertyHelper.MakeFastPropertySetter(publicProperty); var protectedPropertySetter = PropertyHelper.MakeFastPropertySetter(protectedProperty); // Act publicPropertySetter(instance, "TestPublic"); protectedPropertySetter(instance, "TestProtected"); // Assert Assert.Equal("TestPublic", instance.PropA); Assert.Equal("TestProtected", instance.GetPropProtected()); } [Fact] public void MakeFastPropertySetter_SetsPropertyValues_ForOverridenProperties() { // Arrange var instance = new DerivedClassWithOverride(); var typeInfo = instance.GetType().GetTypeInfo(); var property = typeInfo.GetDeclaredProperty("PropA"); var propertySetter = PropertyHelper.MakeFastPropertySetter(property); // Act propertySetter(instance, "Test value"); // Assert Assert.Equal("OverridenTest value", instance.PropA); } [Fact] public void MakeFastPropertySetter_SetsPropertyValues_ForNewedProperties() { // Arrange var instance = new DerivedClassWithNew(); var typeInfo = instance.GetType().GetTypeInfo(); var property = typeInfo.GetDeclaredProperty("PropB"); var propertySetter = PropertyHelper.MakeFastPropertySetter(property); // Act propertySetter(instance, "Test value"); // Assert Assert.Equal("NewedTest value", instance.PropB); } [Fact] public void MakeFastPropertyGetter_ReferenceType_ForNullObject_Throws() { // Arrange var property = PropertyHelper .GetProperties(typeof(BaseClass)) .Single(p => p.Name == nameof(BaseClass.PropA)); var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property); // Act & Assert Assert.Throws(() => accessor(null)); } [Fact] public void MakeFastPropertyGetter_ValueType_ForNullObject_Throws() { // Arrange var property = PropertyHelper .GetProperties(typeof(MyProperties)) .Single(p => p.Name == nameof(MyProperties.StringProp)); var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property); // Act & Assert Assert.Throws(() => accessor(null)); } [Fact] public void MakeNullSafeFastPropertyGetter_ReferenceType_Success() { // Arrange var property = PropertyHelper .GetProperties(typeof(BaseClass)) .Single(p => p.Name == nameof(BaseClass.PropA)); var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); // Act var value = accessor(new BaseClass() { PropA = "Hi" }); // Assert Assert.Equal("Hi", value); } [Fact] public void MakeNullSafeFastPropertyGetter_ValueType_Success() { // Arrange var property = PropertyHelper .GetProperties(typeof(MyProperties)) .Single(p => p.Name == nameof(MyProperties.StringProp)); var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); // Act var value = accessor(new MyProperties() { StringProp = "Hi" }); // Assert Assert.Equal("Hi", value); } [Fact] public void MakeNullSafeFastPropertyGetter_ReferenceType_ForNullObject_ReturnsNull() { // Arrange var property = PropertyHelper .GetProperties(typeof(BaseClass)) .Single(p => p.Name == nameof(BaseClass.PropA)); var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); // Act var value = accessor(null); // Assert Assert.Null(value); } [Fact] public void MakeNullSafeFastPropertyGetter_ValueType_ForNullObject_ReturnsNull() { // Arrange var property = PropertyHelper .GetProperties(typeof(MyProperties)) .Single(p => p.Name == nameof(MyProperties.StringProp)); var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); // Act var value = accessor(null); // Assert Assert.Null(value); } public static TheoryData> IgnoreCaseTestData { get { return new TheoryData> { { new { selected = true, SeLeCtEd = false }, new KeyValuePair("selected", false) }, { new { SeLeCtEd = false, selected = true }, new KeyValuePair("SeLeCtEd", true) }, { new { SelECTeD = false, SeLECTED = true }, new KeyValuePair("SelECTeD", true) } }; } } [Theory] [MemberData(nameof(IgnoreCaseTestData))] public void ObjectToDictionary_IgnoresPropertyCase(object testObject, KeyValuePair expectedEntry) { // Act var result = PropertyHelper.ObjectToDictionary(testObject); // Assert var entry = Assert.Single(result); Assert.Equal(expectedEntry, entry); } [Fact] public void ObjectToDictionary_WithNullObject_ReturnsEmptyDictionary() { // Arrange object value = null; // Act var dictValues = PropertyHelper.ObjectToDictionary(value); // Assert Assert.NotNull(dictValues); Assert.Equal(0, dictValues.Count); } [Fact] public void ObjectToDictionary_WithPlainObjectType_ReturnsEmptyDictionary() { // Arrange var value = new object(); // Act var dictValues = PropertyHelper.ObjectToDictionary(value); // Assert Assert.NotNull(dictValues); Assert.Equal(0, dictValues.Count); } [Fact] public void ObjectToDictionary_WithPrimitiveType_LooksUpPublicProperties() { // Arrange var value = "test"; // Act var dictValues = PropertyHelper.ObjectToDictionary(value); // Assert Assert.NotNull(dictValues); Assert.Equal(1, dictValues.Count); Assert.Equal(4, dictValues["Length"]); } [Fact] public void ObjectToDictionary_WithAnonymousType_LooksUpProperties() { // Arrange var value = new { test = "value", other = 1 }; // Act var dictValues = PropertyHelper.ObjectToDictionary(value); // Assert Assert.NotNull(dictValues); Assert.Equal(2, dictValues.Count); Assert.Equal("value", dictValues["test"]); Assert.Equal(1, dictValues["other"]); } [Fact] public void ObjectToDictionary_ReturnsCaseInsensitiveDictionary() { // Arrange var value = new { TEST = "value", oThEr = 1 }; // Act var dictValues = PropertyHelper.ObjectToDictionary(value); // Assert Assert.NotNull(dictValues); Assert.Equal(2, dictValues.Count); Assert.Equal("value", dictValues["test"]); Assert.Equal(1, dictValues["other"]); } [Fact] public void ObjectToDictionary_ReturnsInheritedProperties() { // Arrange var value = new ThreeDPoint() { X = 5, Y = 10, Z = 17 }; // Act var dictValues = PropertyHelper.ObjectToDictionary(value); // Assert Assert.NotNull(dictValues); Assert.Equal(3, dictValues.Count); Assert.Equal(5, dictValues["X"]); Assert.Equal(10, dictValues["Y"]); Assert.Equal(17, dictValues["Z"]); } private class Point { public int X { get; set; } public int Y { get; set; } } private class ThreeDPoint : Point { public int Z { get; set; } } private class Static { public static int Prop2 { get; set; } public int Prop5 { get; set; } } #if NETSTANDARD || NETCOREAPP private class RefStructProperties { public Span Span => throw new NotImplementedException(); public MyRefStruct UserDefined => throw new NotImplementedException(); public int Prop5 { get; set; } } private readonly ref struct MyRefStruct { } #elif NET46 || NET461 #else #error Unknown TFM - update the set of TFMs where we test for ref structs #endif private struct MyProperties { public int IntProp { get; set; } public string StringProp { get; set; } } private class SetOnly { public int Prop2 { set { } } public int Prop6 { get; set; } } private class PrivateProperties { public int Prop1 { get; set; } protected int Prop2 { get; set; } private int Prop3 { get; set; } } public class BaseClass { public string PropA { get; set; } protected string PropProtected { get; set; } public string GetPropProtected() { return PropProtected; } } public class DerivedClass : BaseClass { public string PropB { get; set; } } public class BaseClassWithVirtual { public virtual string PropA { get; set; } public string PropB { get; set; } } public class DerivedClassWithNew : BaseClassWithVirtual { private string _value = "Newed"; public new string PropB { get { return _value; } set { _value = "Newed" + value; } } } public class DerivedClassWithOverride : BaseClassWithVirtual { private string _value = "Overriden"; public override string PropA { get { return _value; } set { _value = "Overriden" + value; } } } private class DerivedClassWithNonReadableProperties : BaseClassWithVirtual { public string this[int index] { get { return string.Empty; } set { } } public int Visible { get; set; } private string NotVisible { get; set; } public string NotVisible2 { private get; set; } public string NotVisible3 { set { } } public static string NotVisible4 { get; set; } } private struct MyStruct { public string Foo { get; set; } } private class BaseHiddenProperty { public int Id { get; set; } } private class DerivedHiddenProperty : BaseHiddenProperty { public new string Id { get; set; } public string Name { get; set; } } private class DerivedHiddenProperty2 : DerivedHiddenProperty { public new Guid Id { get; set; } public new string Name { get; private set; } } } }