From 604c752ef321c7f31c2016427b54f04eb05615f0 Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Wed, 8 May 2019 15:56:51 +0300 Subject: [PATCH 01/18] [WIP]: AutoSetupProperties switch --- src/Moq/Extensions.cs | 62 +++-------- src/Moq/Interception/InterceptionAspects.cs | 114 ++++++++++++++++++++ src/Moq/Interception/Mock.cs | 5 + src/Moq/Mock.cs | 83 +------------- src/Moq/Switches.cs | 5 + 5 files changed, 137 insertions(+), 132 deletions(-) diff --git a/src/Moq/Extensions.cs b/src/Moq/Extensions.cs index a2d041fa9..6da9100aa 100644 --- a/src/Moq/Extensions.cs +++ b/src/Moq/Extensions.cs @@ -63,6 +63,18 @@ public static bool IsPropertySetter(this MethodInfo method) return method.IsSpecialName && method.Name.StartsWith("set_", StringComparison.Ordinal); } + public static bool IsPropertyAccessor(this MethodInfo method) + { + return method.IsPropertyGetter() + || method.IsPropertySetter(); + } + + public static bool IsPropertyIndexerAccessor(this MethodInfo method) + { + return method.IsPropertyIndexerGetter() + || method.IsPropertyIndexerSetter(); + } + // NOTE: The following two methods used to first check whether `method.IsSpecialName` was set // as a quick guard against non-event accessor methods. This was removed in commit 44070a90 // to "increase compatibility with F# and COM". More specifically: @@ -223,56 +235,6 @@ private static MethodInfo GetInvokeMethodFromUntypedDelegateCallback(Delegate ca } } - /// - /// Gets all properties of the specified type in depth-first order. - /// That is, properties of the furthest ancestors are returned first, - /// and the type's own properties are returned last. - /// - /// The type whose properties are to be returned. - internal static List GetAllPropertiesInDepthFirstOrder(this Type type) - { - var properties = new List(); - var none = new HashSet(); - - type.AddPropertiesInDepthFirstOrderTo(properties, typesAlreadyVisited: none); - - return properties; - } - - /// - /// This is a helper method supporting - /// and is not supposed to be called directly. - /// - private static void AddPropertiesInDepthFirstOrderTo(this Type type, List properties, HashSet typesAlreadyVisited) - { - if (!typesAlreadyVisited.Contains(type)) - { - // make sure we do not process properties of the current type twice: - typesAlreadyVisited.Add(type); - - //// follow down axis 1: add properties of base class. note that this is currently - //// disabled, since it wasn't done previously and this can only result in changed - //// behavior. - //if (type.BaseType != null) - //{ - // type.BaseType.AddPropertiesInDepthFirstOrderTo(properties, typesAlreadyVisited); - //} - - // follow down axis 2: add properties of inherited / implemented interfaces: - var superInterfaceTypes = type.GetInterfaces(); - foreach (var superInterfaceType in superInterfaceTypes) - { - superInterfaceType.AddPropertiesInDepthFirstOrderTo(properties, typesAlreadyVisited); - } - - // add own properties: - foreach (var property in type.GetProperties()) - { - properties.Add(property); - } - } - } - public static bool TryFind(this IEnumerable innerMockSetups, InvocationShape expectation, out Setup setup) { Debug.Assert(innerMockSetups.All(s => s.ReturnsInnerMock(out _))); diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index ade01c398..c6148c7a5 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; using System.Reflection; namespace Moq @@ -331,4 +332,117 @@ public static void Handle(Invocation invocation, Mock mock) } } } + + internal static class HandleAutoSetupProperties + { + private static readonly int AccessorPrefixLength = "?et_".Length; // get_ or set_ + + public static void Handle(Invocation invocation, Mock mock) + { + MethodInfo invocationMethod = invocation.Method; + if (invocationMethod.IsPropertyAccessor() && !invocationMethod.IsPropertyIndexerAccessor()) + { + if (mock.Setups.FindMatchFor(invocation) == null) + { + PropertyInfo property = GetPropertyFromAccessorMethod(mock, invocationMethod, out Type propertyHolderType); + var expression = GetPropertyExpression(propertyHolderType, property); + var getter = property.GetGetMethod(true); + + object value = null; + bool valueNotSet = true; + + mock.Setups.Add(new AutoImplementedPropertyGetterSetup(expression, getter, () => + { + if (valueNotSet) + { + object initialValue; + Mock innerMock; + try + { + initialValue = mock.GetDefaultValue(getter, out innerMock, + useAlternateProvider: mock.DefaultValueProvider); + } + catch + { + // Since this method performs a batch operation, a single failure of the default value + // provider should not tear down the whole operation. The empty default value provider + // is a safe fallback because it does not throw. + initialValue = mock.GetDefaultValue(getter, out innerMock, + useAlternateProvider: DefaultValueProvider.Empty); + } + + if (innerMock != null) + { + Mock.SetupAllProperties(innerMock); + } + + value = initialValue; + valueNotSet = false; + } + + return value; + })); + + if (property.CanWrite) + { + mock.Setups.Add(new AutoImplementedPropertySetterSetup(expression, property.GetSetMethod(true), + (newValue) => + { + value = newValue; + valueNotSet = false; + })); + } + } + } + } + + private static PropertyInfo GetPropertyFromAccessorMethod(Mock mock, MethodInfo accessorMethod, out Type propertyHolderType) + { + string propertyNameToSearch = accessorMethod.Name.Substring(AccessorPrefixLength); + Type mockedType = mock.MockedType; + + PropertyInfo result = mockedType.GetProperty(propertyNameToSearch); + propertyHolderType = mockedType; + + if (result == null) + { + if (mockedType.IsInterface) + result = SearchPropertyInInterfaces(mock.InheritedInterfaces, propertyNameToSearch, out _); + if (result == null) + result = SearchPropertyInInterfaces(mock.AdditionalInterfaces, propertyNameToSearch, out propertyHolderType); + } + + return result; + } + + private static PropertyInfo SearchPropertyInInterfaces(IEnumerable interfaces, string propertyNameToSearch, out Type propertyHolderType) + { + foreach (Type @interface in interfaces) + { + PropertyInfo property = @interface.GetProperty(propertyNameToSearch); + if (property != null) + { + propertyHolderType = @interface; + return property; + } + + Type[] parentInterfaces = @interface.GetInterfaces(); + if (parentInterfaces.Any()) + { + property = SearchPropertyInInterfaces(parentInterfaces, propertyNameToSearch, out propertyHolderType); + if (property != null) + return property; + } + } + + propertyHolderType = null; + return null; + } + + private static LambdaExpression GetPropertyExpression(Type mockType, PropertyInfo property) + { + var param = Expression.Parameter(mockType, "m"); + return Expression.Lambda(Expression.MakeMemberAccess(param, property), param); + } + } } diff --git a/src/Moq/Interception/Mock.cs b/src/Moq/Interception/Mock.cs index dcdc4b687..655af5ec5 100644 --- a/src/Moq/Interception/Mock.cs +++ b/src/Moq/Interception/Mock.cs @@ -17,6 +17,11 @@ void IInterceptor.Intercept(Invocation invocation) return; } + if (Switches.HasFlag(Switches.AutoSetupProperties)) + { + HandleAutoSetupProperties.Handle(invocation, this); + } + RecordInvocation.Handle(invocation, this); if (FindAndExecuteMatchingSetup.Handle(invocation, this)) diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index 0cb3c6884..894d5fb4c 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -494,88 +494,7 @@ private static TSetup SetupRecursive(Mock mock, LambdaExpression express internal static void SetupAllProperties(Mock mock) { - Mock.SetupAllProperties(mock, mock.DefaultValueProvider); - // ^^^^^^^^^^^^^^^^^^^^^^^^^ - // `SetupAllProperties` no longer performs eager recursive property setup like in previous versions. - // If `mock` uses `DefaultValue.Mock`, mocked sub-objects are now only constructed when queried for - // the first time. In order for `SetupAllProperties`'s new mode of operation to be indistinguishable - // from how it worked previously, it's important to capture the default value provider at this precise - // moment, since it might be changed later (before queries to properties of a mockable type). - } - - private static void SetupAllProperties(Mock mock, DefaultValueProvider defaultValueProvider) - { - var mockType = mock.MockedType; - - var properties = - mockType - .GetAllPropertiesInDepthFirstOrder() - // ^ Depth-first traversal is important because properties in derived interfaces - // that shadow properties in base interfaces should be set up last. This - // enables the use case where a getter-only property is redeclared in a derived - // interface as a getter-and-setter property. - .Where(p => - p.CanRead && p.CanOverrideGet() && - p.CanWrite == p.CanOverrideSet() && - // ^ This condition will be true for two kinds of properties: - // (a) those that are read-only; and - // (b) those that are writable and whose setter can be overridden. - p.GetIndexParameters().Length == 0 && - ProxyFactory.Instance.IsMethodVisible(p.GetGetMethod(), out _)) - .Distinct(); - - foreach (var property in properties) - { - var expression = GetPropertyExpression(mockType, property); - var getter = property.GetGetMethod(true); - - object value = null; - bool valueNotSet = true; - - mock.Setups.Add(new AutoImplementedPropertyGetterSetup(expression, getter, () => - { - if (valueNotSet) - { - object initialValue; - Mock innerMock; - try - { - initialValue = mock.GetDefaultValue(getter, out innerMock, useAlternateProvider: defaultValueProvider); - } - catch - { - // Since this method performs a batch operation, a single failure of the default value - // provider should not tear down the whole operation. The empty default value provider - // is a safe fallback because it does not throw. - initialValue = mock.GetDefaultValue(getter, out innerMock, useAlternateProvider: DefaultValueProvider.Empty); - } - - if (innerMock != null) - { - Mock.SetupAllProperties(innerMock, defaultValueProvider); - } - - value = initialValue; - valueNotSet = false; - } - return value; - })); - - if (property.CanWrite) - { - mock.Setups.Add(new AutoImplementedPropertySetterSetup(expression, property.GetSetMethod(true), (newValue) => - { - value = newValue; - valueNotSet = false; - })); - } - } - } - - private static LambdaExpression GetPropertyExpression(Type mockType, PropertyInfo property) - { - var param = Expression.Parameter(mockType, "m"); - return Expression.Lambda(Expression.MakeMemberAccess(param, property), param); + mock.Switches |= Switches.AutoSetupProperties; } #endregion diff --git a/src/Moq/Switches.cs b/src/Moq/Switches.cs index 01cdaedcc..a727c13fb 100644 --- a/src/Moq/Switches.cs +++ b/src/Moq/Switches.cs @@ -22,5 +22,10 @@ public enum Switches /// This results in more helpful error messages, but may affect performance. /// CollectDiagnosticFileInfoForSetups = 1 << 0, + + /// + /// When enabled, accesses to properties without a setup will cause a setup to be added on-demand. + /// + AutoSetupProperties = 1 << 1 } } From dd4d90a4363f6dfc75f106b1cf9e6e70d3316b6e Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Thu, 9 May 2019 15:49:09 +0300 Subject: [PATCH 02/18] Add failing test for write-only property --- tests/Moq.Tests/StubExtensionsFixture.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/Moq.Tests/StubExtensionsFixture.cs b/tests/Moq.Tests/StubExtensionsFixture.cs index af2b3a3c9..e1b2e6607 100644 --- a/tests/Moq.Tests/StubExtensionsFixture.cs +++ b/tests/Moq.Tests/StubExtensionsFixture.cs @@ -149,6 +149,20 @@ public void Property_stubbed_by_SetupAllProperties_during_DefaultValue_Mock_can_ Assert.Equal(2, mock.Object.Bar.Value); } + public abstract class WriteOnlyProperty + { + public abstract string Test { set; } + } + + [Fact] + public void Write_only_property_should_be_ignored_by_SetupAllProperties() + { + var mock = new Mock(); + mock.SetupAllProperties(); + + mock.Object.Test = "test"; + } + private object GetValue() { return new object(); } public interface IFoo From ded995bc1f160d84d129cd6ab0d2211e742bd7a7 Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Thu, 9 May 2019 15:55:17 +0300 Subject: [PATCH 03/18] Fix SetupAllProperties switch for write-only properties --- src/Moq/Interception/InterceptionAspects.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index c6148c7a5..2d9adaa7b 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -345,8 +345,15 @@ public static void Handle(Invocation invocation, Mock mock) if (mock.Setups.FindMatchFor(invocation) == null) { PropertyInfo property = GetPropertyFromAccessorMethod(mock, invocationMethod, out Type propertyHolderType); - var expression = GetPropertyExpression(propertyHolderType, property); + + // Should ignore write-only properties, as they will be handled by Return aspect + if (invocationMethod.IsPropertySetter() && !property.CanRead) + { + return; + } + var getter = property.GetGetMethod(true); + var expression = GetPropertyExpression(propertyHolderType, property); object value = null; bool valueNotSet = true; From b2f756f60ed79bda8aad3f606f1763707ccbd52c Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Thu, 9 May 2019 16:09:37 +0300 Subject: [PATCH 04/18] Add failing test for DefaultValue change --- tests/Moq.Tests/StubExtensionsFixture.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Moq.Tests/StubExtensionsFixture.cs b/tests/Moq.Tests/StubExtensionsFixture.cs index e1b2e6607..c01995763 100644 --- a/tests/Moq.Tests/StubExtensionsFixture.cs +++ b/tests/Moq.Tests/StubExtensionsFixture.cs @@ -148,6 +148,17 @@ public void Property_stubbed_by_SetupAllProperties_during_DefaultValue_Mock_can_ mock.Object.Bar = new Bar { Value = 2 }; Assert.Equal(2, mock.Object.Bar.Value); } + + [Fact] + public void Property_stubbed_by_SetupAllProperties_should_capture_default_value_behaviour() + { + var mock = new Mock() { DefaultValue = DefaultValue.Mock }; + mock.SetupAllProperties(); + mock.DefaultValue = DefaultValue.Empty; + + mock.Object.Bar.Value = 5; + Assert.Equal(5, mock.Object.Bar.Value); + } public abstract class WriteOnlyProperty { From cb6eb7c5d49b5cc9dafd694c8b4e9d3e2c6cc48c Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Thu, 9 May 2019 16:13:26 +0300 Subject: [PATCH 05/18] Handle default value change --- src/Moq/Interception/InterceptionAspects.cs | 2 +- src/Moq/Mock.cs | 13 +++++++++++++ tests/Moq.Tests/StubExtensionsFixture.cs | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index 2d9adaa7b..a0beadd9e 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -367,7 +367,7 @@ public static void Handle(Invocation invocation, Mock mock) try { initialValue = mock.GetDefaultValue(getter, out innerMock, - useAlternateProvider: mock.DefaultValueProvider); + useAlternateProvider: mock.AutoSetupPropertiesDefaultValueProvider); } catch { diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index 894d5fb4c..0a2b677d6 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -171,6 +171,12 @@ public DefaultValue DefaultValue /// e. g. to produce default return values for unexpected invocations. /// public abstract DefaultValueProvider DefaultValueProvider { get; set; } + + /// + /// Used to capture at the moment of call + /// to align new "on-demand" implementation with existing behaviour. + /// + internal DefaultValueProvider AutoSetupPropertiesDefaultValueProvider { get; private set; } internal abstract SetupCollection Setups { get; } @@ -495,6 +501,13 @@ private static TSetup SetupRecursive(Mock mock, LambdaExpression express internal static void SetupAllProperties(Mock mock) { mock.Switches |= Switches.AutoSetupProperties; + + mock.AutoSetupPropertiesDefaultValueProvider = mock.DefaultValueProvider; + // `SetupAllProperties` no longer performs property setup like in previous versions. + // Instead it just enables a switch to setup properties on-demand at the moment of first access. + // In order for `SetupAllProperties`'s new mode of operation to be indistinguishable + // from how it worked previously, it's important to capture the default value provider at this precise + // moment, since it might be changed later (before queries to properties). } #endregion diff --git a/tests/Moq.Tests/StubExtensionsFixture.cs b/tests/Moq.Tests/StubExtensionsFixture.cs index c01995763..b1ed87f22 100644 --- a/tests/Moq.Tests/StubExtensionsFixture.cs +++ b/tests/Moq.Tests/StubExtensionsFixture.cs @@ -150,7 +150,7 @@ public void Property_stubbed_by_SetupAllProperties_during_DefaultValue_Mock_can_ } [Fact] - public void Property_stubbed_by_SetupAllProperties_should_capture_default_value_behaviour() + public void Property_stubbed_by_SetupAllProperties_should_capture_DefaultValue_behaviour() { var mock = new Mock() { DefaultValue = DefaultValue.Mock }; mock.SetupAllProperties(); From 352a8e2f3c3cc7ea54ecec8e07e7261d01057630 Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Thu, 9 May 2019 16:47:46 +0300 Subject: [PATCH 06/18] Handle default value change for inner mocks --- src/Moq/Interception/InterceptionAspects.cs | 2 +- src/Moq/Mock.cs | 9 +++++++-- tests/Moq.Tests/StubExtensionsFixture.cs | 10 ++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index a0beadd9e..30b26f955 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -380,7 +380,7 @@ public static void Handle(Invocation invocation, Mock mock) if (innerMock != null) { - Mock.SetupAllProperties(innerMock); + Mock.SetupAllProperties(innerMock, mock.AutoSetupPropertiesDefaultValueProvider); } value = initialValue; diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index 0a2b677d6..cfbb6909d 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -499,11 +499,16 @@ private static TSetup SetupRecursive(Mock mock, LambdaExpression express } internal static void SetupAllProperties(Mock mock) + { + SetupAllProperties(mock, mock.DefaultValueProvider); + } + + internal static void SetupAllProperties(Mock mock, DefaultValueProvider defaultValueProvider) { mock.Switches |= Switches.AutoSetupProperties; - mock.AutoSetupPropertiesDefaultValueProvider = mock.DefaultValueProvider; - // `SetupAllProperties` no longer performs property setup like in previous versions. + mock.AutoSetupPropertiesDefaultValueProvider = defaultValueProvider; + // `SetupAllProperties` no longer performs properties setup like in previous versions. // Instead it just enables a switch to setup properties on-demand at the moment of first access. // In order for `SetupAllProperties`'s new mode of operation to be indistinguishable // from how it worked previously, it's important to capture the default value provider at this precise diff --git a/tests/Moq.Tests/StubExtensionsFixture.cs b/tests/Moq.Tests/StubExtensionsFixture.cs index b1ed87f22..2d8596b0a 100644 --- a/tests/Moq.Tests/StubExtensionsFixture.cs +++ b/tests/Moq.Tests/StubExtensionsFixture.cs @@ -159,6 +159,16 @@ public void Property_stubbed_by_SetupAllProperties_should_capture_DefaultValue_b mock.Object.Bar.Value = 5; Assert.Equal(5, mock.Object.Bar.Value); } + + [Fact] + public void Property_stubbed_by_SetupAllProperties_should_capture_DefaultValue_behaviour_for_inner_mocks() + { + var mock = new Mock() { DefaultValue = DefaultValue.Mock }; + mock.SetupAllProperties(); + mock.DefaultValue = DefaultValue.Empty; + + Assert.NotNull(mock.Object.Hierarchy.Hierarchy); + } public abstract class WriteOnlyProperty { From 3838eae6ca73df1a15b1ae4a0ab7b0446a92a43c Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Thu, 9 May 2019 16:53:37 +0300 Subject: [PATCH 07/18] Update comments --- src/Moq/Interception/InterceptionAspects.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index 30b26f955..d01883fcf 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -340,13 +340,14 @@ internal static class HandleAutoSetupProperties public static void Handle(Invocation invocation, Mock mock) { MethodInfo invocationMethod = invocation.Method; + // Original implementation with eager setup skipped indexers too. if (invocationMethod.IsPropertyAccessor() && !invocationMethod.IsPropertyIndexerAccessor()) { if (mock.Setups.FindMatchFor(invocation) == null) { PropertyInfo property = GetPropertyFromAccessorMethod(mock, invocationMethod, out Type propertyHolderType); - // Should ignore write-only properties, as they will be handled by Return aspect + // Should ignore write-only properties, as they will be handled by Return aspect. if (invocationMethod.IsPropertySetter() && !property.CanRead) { return; @@ -408,13 +409,19 @@ private static PropertyInfo GetPropertyFromAccessorMethod(Mock mock, MethodInfo string propertyNameToSearch = accessorMethod.Name.Substring(AccessorPrefixLength); Type mockedType = mock.MockedType; + // Firstly try to search directly in mocked type. PropertyInfo result = mockedType.GetProperty(propertyNameToSearch); propertyHolderType = mockedType; if (result == null) { + // Interfaces do not simply return properties from base interfaces, so need to search there directly. if (mockedType.IsInterface) result = SearchPropertyInInterfaces(mock.InheritedInterfaces, propertyNameToSearch, out _); + // If there are additional interfaces applied via As, we also need to look there. + // At this point we need to capture interface type in which property was found. It is required to build + // property accessor expression later in GetPropertyExpression (mocked type can not be used in this case, + // since the property was found not in mocked type, but in additional interface. if (result == null) result = SearchPropertyInInterfaces(mock.AdditionalInterfaces, propertyNameToSearch, out propertyHolderType); } From d483f4e31f2de692298f046d6d6451c721d9355e Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Thu, 9 May 2019 16:56:29 +0300 Subject: [PATCH 08/18] Formatting --- src/Moq/Extensions.cs | 6 ++---- src/Moq/Mock.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Moq/Extensions.cs b/src/Moq/Extensions.cs index 6da9100aa..d1089e09a 100644 --- a/src/Moq/Extensions.cs +++ b/src/Moq/Extensions.cs @@ -65,14 +65,12 @@ public static bool IsPropertySetter(this MethodInfo method) public static bool IsPropertyAccessor(this MethodInfo method) { - return method.IsPropertyGetter() - || method.IsPropertySetter(); + return method.IsPropertyGetter() || method.IsPropertySetter(); } public static bool IsPropertyIndexerAccessor(this MethodInfo method) { - return method.IsPropertyIndexerGetter() - || method.IsPropertyIndexerSetter(); + return method.IsPropertyIndexerGetter() || method.IsPropertyIndexerSetter(); } // NOTE: The following two methods used to first check whether `method.IsSpecialName` was set diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index cfbb6909d..bb6f19de0 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -502,7 +502,7 @@ internal static void SetupAllProperties(Mock mock) { SetupAllProperties(mock, mock.DefaultValueProvider); } - + internal static void SetupAllProperties(Mock mock, DefaultValueProvider defaultValueProvider) { mock.Switches |= Switches.AutoSetupProperties; From 46a5165e7ff11b9b6263fcd852aa864c006fef32 Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Thu, 9 May 2019 17:14:18 +0300 Subject: [PATCH 09/18] Add SetupAllProperties_should_enable_AutoSetupProperties_switch --- tests/Moq.Tests/StubExtensionsFixture.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Moq.Tests/StubExtensionsFixture.cs b/tests/Moq.Tests/StubExtensionsFixture.cs index 2d8596b0a..2c2a8c75a 100644 --- a/tests/Moq.Tests/StubExtensionsFixture.cs +++ b/tests/Moq.Tests/StubExtensionsFixture.cs @@ -184,6 +184,15 @@ public void Write_only_property_should_be_ignored_by_SetupAllProperties() mock.Object.Test = "test"; } + [Fact] + public void SetupAllProperties_should_enable_AutoSetupProperties_switch() + { + var mock = new Mock(); + mock.SetupAllProperties(); + + Assert.Equal(Switches.AutoSetupProperties, mock.Switches & Switches.AutoSetupProperties); + } + private object GetValue() { return new object(); } public interface IFoo From 6c0853797e03473c70188c62e6f7f04a90609aee Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Fri, 10 May 2019 15:41:08 +0300 Subject: [PATCH 10/18] Add tests to cover properties with write access added in derived class --- tests/Moq.Tests/StubExtensionsFixture.cs | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/Moq.Tests/StubExtensionsFixture.cs b/tests/Moq.Tests/StubExtensionsFixture.cs index 2c2a8c75a..32738577f 100644 --- a/tests/Moq.Tests/StubExtensionsFixture.cs +++ b/tests/Moq.Tests/StubExtensionsFixture.cs @@ -192,6 +192,42 @@ public void SetupAllProperties_should_enable_AutoSetupProperties_switch() Assert.Equal(Switches.AutoSetupProperties, mock.Switches & Switches.AutoSetupProperties); } + + public interface IWithReadOnlyProperty + { + string WriteAccessInDerived { get; } + } + + public abstract class AddWriteAccessToInterface : IWithReadOnlyProperty + { + public abstract string WriteAccessInDerived { get; set; } + } + + [Fact] + public void SetupAllProperties_should_setup_properties_from_interface_with_write_access_added_in_derived() + { + var mock = new Mock(); + mock.SetupAllProperties(); + IWithReadOnlyProperty asInterface = mock.Object; + + mock.Object.WriteAccessInDerived = "test"; + + Assert.Equal("test", mock.Object.WriteAccessInDerived); + Assert.Equal("test", asInterface.WriteAccessInDerived); + } + + [Fact] + public void SetupAllProperties_should_setup_properties_from_interface_with_write_access_added_in_derived_if_interface_is_reimplemented() + { + var mock = new Mock(); + mock.SetupAllProperties(); + IWithReadOnlyProperty asReimplementedInterface = mock.As().Object; + + mock.Object.WriteAccessInDerived = "test"; + + Assert.Equal("test", mock.Object.WriteAccessInDerived); + Assert.Equal("test", asReimplementedInterface.WriteAccessInDerived); + } private object GetValue() { return new object(); } From 6d5aa4a52695792d629a0f0c3296184ec67ebeea Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Sat, 1 Jun 2019 11:08:53 +0300 Subject: [PATCH 11/18] Update interception pipeline to handle auto setup properties --- src/Moq/Interception/InterceptionAspects.cs | 130 +++++++++++--------- src/Moq/Interception/Mock.cs | 12 +- 2 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index d01883fcf..a42805d92 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -122,10 +122,6 @@ public static bool Handle(Invocation invocation, Mock mock) matchedSetup.Execute(invocation); return true; } - else if (mock.Behavior == MockBehavior.Strict) - { - throw MockException.NoSetup(invocation); - } else { return false; @@ -337,71 +333,84 @@ internal static class HandleAutoSetupProperties { private static readonly int AccessorPrefixLength = "?et_".Length; // get_ or set_ - public static void Handle(Invocation invocation, Mock mock) + public static bool Handle(Invocation invocation, Mock mock) { + if (!mock.Switches.HasFlag(Switches.AutoSetupProperties)) + { + return false; + } + MethodInfo invocationMethod = invocation.Method; - // Original implementation with eager setup skipped indexers too. if (invocationMethod.IsPropertyAccessor() && !invocationMethod.IsPropertyIndexerAccessor()) { - if (mock.Setups.FindMatchFor(invocation) == null) + PropertyInfo property = GetPropertyFromAccessorMethod(mock, invocationMethod, out Type propertyHolderType); + + if (property == null) { - PropertyInfo property = GetPropertyFromAccessorMethod(mock, invocationMethod, out Type propertyHolderType); + return false; + } - // Should ignore write-only properties, as they will be handled by Return aspect. - if (invocationMethod.IsPropertySetter() && !property.CanRead) - { - return; - } - - var getter = property.GetGetMethod(true); - var expression = GetPropertyExpression(propertyHolderType, property); + // Should ignore write-only properties, as they will be handled by Return aspect. + if (invocationMethod.IsPropertySetter() && !property.CanRead) + { + return false; + } + + var getter = property.GetGetMethod(true); + var expression = GetPropertyExpression(propertyHolderType, property); - object value = null; - bool valueNotSet = true; + object propertyValue = CreateInitialPropertyValue(mock, getter); - mock.Setups.Add(new AutoImplementedPropertyGetterSetup(expression, getter, () => + Setup getterSetup = new AutoImplementedPropertyGetterSetup(expression, getter, () => propertyValue); + mock.Setups.Add(getterSetup); + + Setup setterSetup = null; + if (property.CanWrite) + { + MethodInfo setter = property.GetSetMethod(nonPublic: true); + setterSetup = new AutoImplementedPropertySetterSetup(expression, setter, (newValue) => { - if (valueNotSet) - { - object initialValue; - Mock innerMock; - try - { - initialValue = mock.GetDefaultValue(getter, out innerMock, - useAlternateProvider: mock.AutoSetupPropertiesDefaultValueProvider); - } - catch - { - // Since this method performs a batch operation, a single failure of the default value - // provider should not tear down the whole operation. The empty default value provider - // is a safe fallback because it does not throw. - initialValue = mock.GetDefaultValue(getter, out innerMock, - useAlternateProvider: DefaultValueProvider.Empty); - } + propertyValue = newValue; + }); + mock.Setups.Add(setterSetup); + } - if (innerMock != null) - { - Mock.SetupAllProperties(innerMock, mock.AutoSetupPropertiesDefaultValueProvider); - } + Setup setupToExecute = invocationMethod.IsPropertyGetter() ? getterSetup : setterSetup; + setupToExecute.Execute(invocation); - value = initialValue; - valueNotSet = false; - } + return true; + } + else + { + return false; + } + } - return value; - })); + private static object CreateInitialPropertyValue(Mock mock, MethodInfo getter) + { + object initialValue; + Mock innerMock; + try + { + initialValue = mock.GetDefaultValue(getter, out innerMock, + useAlternateProvider: mock.AutoSetupPropertiesDefaultValueProvider); + } + catch + { + // TODO: revisit this catch + // Since this method performs a batch operation, a single failure of the default value + // provider should not tear down the whole operation. The empty default value provider + // is a safe fallback because it does not throw. + initialValue = mock.GetDefaultValue(getter, out innerMock, + useAlternateProvider: DefaultValueProvider.Empty); + } - if (property.CanWrite) - { - mock.Setups.Add(new AutoImplementedPropertySetterSetup(expression, property.GetSetMethod(true), - (newValue) => - { - value = newValue; - valueNotSet = false; - })); - } - } + if (innerMock != null) + { + Mock.SetupAllProperties(innerMock, mock.AutoSetupPropertiesDefaultValueProvider); } + + return initialValue; } private static PropertyInfo GetPropertyFromAccessorMethod(Mock mock, MethodInfo accessorMethod, out Type propertyHolderType) @@ -459,4 +468,15 @@ private static LambdaExpression GetPropertyExpression(Type mockType, PropertyInf return Expression.Lambda(Expression.MakeMemberAccess(param, property), param); } } + + internal static class FailForStrictMock + { + public static void Handle(Invocation invocation, Mock mock) + { + if (mock.Behavior == MockBehavior.Strict) + { + throw MockException.NoSetup(invocation); + } + } + } } diff --git a/src/Moq/Interception/Mock.cs b/src/Moq/Interception/Mock.cs index 655af5ec5..4a7577142 100644 --- a/src/Moq/Interception/Mock.cs +++ b/src/Moq/Interception/Mock.cs @@ -17,17 +17,19 @@ void IInterceptor.Intercept(Invocation invocation) return; } - if (Switches.HasFlag(Switches.AutoSetupProperties)) - { - HandleAutoSetupProperties.Handle(invocation, this); - } - RecordInvocation.Handle(invocation, this); if (FindAndExecuteMatchingSetup.Handle(invocation, this)) { return; } + + if (HandleAutoSetupProperties.Handle(invocation, this)) + { + return; + } + + FailForStrictMock.Handle(invocation, this); Return.Handle(invocation, this); } From 7b5e5f6259e58e2b791339a2e1ec10fd207f05f6 Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Sat, 1 Jun 2019 11:18:36 +0300 Subject: [PATCH 12/18] Remove new flag from Switches and use AutoSetupPropertiesDefaultValueProvider as a switch --- src/Moq/AsInterface.cs | 6 ++++++ src/Moq/Interception/InterceptionAspects.cs | 2 +- src/Moq/Mock.Generic.cs | 2 ++ src/Moq/Mock.cs | 11 +++++------ src/Moq/Switches.cs | 7 +------ tests/Moq.Tests/StubExtensionsFixture.cs | 9 --------- 6 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Moq/AsInterface.cs b/src/Moq/AsInterface.cs index 41259de14..b5554d028 100644 --- a/src/Moq/AsInterface.cs +++ b/src/Moq/AsInterface.cs @@ -46,6 +46,12 @@ public override DefaultValueProvider DefaultValueProvider set => this.owner.DefaultValueProvider = value; } + internal override DefaultValueProvider AutoSetupPropertiesDefaultValueProvider + { + get => this.owner.AutoSetupPropertiesDefaultValueProvider; + set => this.owner.AutoSetupPropertiesDefaultValueProvider = value; + } + internal override EventHandlerCollection EventHandlers => this.owner.EventHandlers; internal override Type[] InheritedInterfaces => this.owner.InheritedInterfaces; diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index a42805d92..f9fa3110d 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -335,7 +335,7 @@ internal static class HandleAutoSetupProperties public static bool Handle(Invocation invocation, Mock mock) { - if (!mock.Switches.HasFlag(Switches.AutoSetupProperties)) + if (mock.AutoSetupPropertiesDefaultValueProvider == null) { return false; } diff --git a/src/Moq/Mock.Generic.cs b/src/Moq/Mock.Generic.cs index 4bcff4d15..256085d3f 100644 --- a/src/Moq/Mock.Generic.cs +++ b/src/Moq/Mock.Generic.cs @@ -172,6 +172,8 @@ public override DefaultValueProvider DefaultValueProvider set => this.defaultValueProvider = value ?? throw new ArgumentNullException(nameof(value)); } + internal override DefaultValueProvider AutoSetupPropertiesDefaultValueProvider { get; set; } + internal override EventHandlerCollection EventHandlers => this.eventHandlers; internal override List AdditionalInterfaces => this.additionalInterfaces; diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index bb6f19de0..a7dd5b92b 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -171,12 +171,13 @@ public DefaultValue DefaultValue /// e. g. to produce default return values for unexpected invocations. /// public abstract DefaultValueProvider DefaultValueProvider { get; set; } - + /// - /// Used to capture at the moment of call - /// to align new "on-demand" implementation with existing behaviour. + /// The used to initialize automatically stubbed properties. + /// It is equal to the value of at the time when + /// was last called. /// - internal DefaultValueProvider AutoSetupPropertiesDefaultValueProvider { get; private set; } + internal abstract DefaultValueProvider AutoSetupPropertiesDefaultValueProvider { get; set; } internal abstract SetupCollection Setups { get; } @@ -505,8 +506,6 @@ internal static void SetupAllProperties(Mock mock) internal static void SetupAllProperties(Mock mock, DefaultValueProvider defaultValueProvider) { - mock.Switches |= Switches.AutoSetupProperties; - mock.AutoSetupPropertiesDefaultValueProvider = defaultValueProvider; // `SetupAllProperties` no longer performs properties setup like in previous versions. // Instead it just enables a switch to setup properties on-demand at the moment of first access. diff --git a/src/Moq/Switches.cs b/src/Moq/Switches.cs index a727c13fb..4bbee90a9 100644 --- a/src/Moq/Switches.cs +++ b/src/Moq/Switches.cs @@ -21,11 +21,6 @@ public enum Switches /// When enabled, specifies that source file information should be collected for each setup. /// This results in more helpful error messages, but may affect performance. /// - CollectDiagnosticFileInfoForSetups = 1 << 0, - - /// - /// When enabled, accesses to properties without a setup will cause a setup to be added on-demand. - /// - AutoSetupProperties = 1 << 1 + CollectDiagnosticFileInfoForSetups = 1 << 0 } } diff --git a/tests/Moq.Tests/StubExtensionsFixture.cs b/tests/Moq.Tests/StubExtensionsFixture.cs index 32738577f..18bac8772 100644 --- a/tests/Moq.Tests/StubExtensionsFixture.cs +++ b/tests/Moq.Tests/StubExtensionsFixture.cs @@ -184,15 +184,6 @@ public void Write_only_property_should_be_ignored_by_SetupAllProperties() mock.Object.Test = "test"; } - [Fact] - public void SetupAllProperties_should_enable_AutoSetupProperties_switch() - { - var mock = new Mock(); - mock.SetupAllProperties(); - - Assert.Equal(Switches.AutoSetupProperties, mock.Switches & Switches.AutoSetupProperties); - } - public interface IWithReadOnlyProperty { string WriteAccessInDerived { get; } From e31d7f08aa62bd875af0b43c14c65a699f87722e Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Sat, 1 Jun 2019 11:39:27 +0300 Subject: [PATCH 13/18] Simplify property lookup --- src/Moq/Interception/InterceptionAspects.cs | 54 ++------------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index f9fa3110d..5e8212da6 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -343,7 +343,8 @@ public static bool Handle(Invocation invocation, Mock mock) MethodInfo invocationMethod = invocation.Method; if (invocationMethod.IsPropertyAccessor() && !invocationMethod.IsPropertyIndexerAccessor()) { - PropertyInfo property = GetPropertyFromAccessorMethod(mock, invocationMethod, out Type propertyHolderType); + string propertyNameToSearch = invocationMethod.Name.Substring(AccessorPrefixLength); + PropertyInfo property = invocationMethod.DeclaringType.GetProperty(propertyNameToSearch); if (property == null) { @@ -357,7 +358,7 @@ public static bool Handle(Invocation invocation, Mock mock) } var getter = property.GetGetMethod(true); - var expression = GetPropertyExpression(propertyHolderType, property); + var expression = GetPropertyExpression(invocationMethod.DeclaringType, property); object propertyValue = CreateInitialPropertyValue(mock, getter); @@ -413,55 +414,6 @@ private static object CreateInitialPropertyValue(Mock mock, MethodInfo getter) return initialValue; } - private static PropertyInfo GetPropertyFromAccessorMethod(Mock mock, MethodInfo accessorMethod, out Type propertyHolderType) - { - string propertyNameToSearch = accessorMethod.Name.Substring(AccessorPrefixLength); - Type mockedType = mock.MockedType; - - // Firstly try to search directly in mocked type. - PropertyInfo result = mockedType.GetProperty(propertyNameToSearch); - propertyHolderType = mockedType; - - if (result == null) - { - // Interfaces do not simply return properties from base interfaces, so need to search there directly. - if (mockedType.IsInterface) - result = SearchPropertyInInterfaces(mock.InheritedInterfaces, propertyNameToSearch, out _); - // If there are additional interfaces applied via As, we also need to look there. - // At this point we need to capture interface type in which property was found. It is required to build - // property accessor expression later in GetPropertyExpression (mocked type can not be used in this case, - // since the property was found not in mocked type, but in additional interface. - if (result == null) - result = SearchPropertyInInterfaces(mock.AdditionalInterfaces, propertyNameToSearch, out propertyHolderType); - } - - return result; - } - - private static PropertyInfo SearchPropertyInInterfaces(IEnumerable interfaces, string propertyNameToSearch, out Type propertyHolderType) - { - foreach (Type @interface in interfaces) - { - PropertyInfo property = @interface.GetProperty(propertyNameToSearch); - if (property != null) - { - propertyHolderType = @interface; - return property; - } - - Type[] parentInterfaces = @interface.GetInterfaces(); - if (parentInterfaces.Any()) - { - property = SearchPropertyInInterfaces(parentInterfaces, propertyNameToSearch, out propertyHolderType); - if (property != null) - return property; - } - } - - propertyHolderType = null; - return null; - } - private static LambdaExpression GetPropertyExpression(Type mockType, PropertyInfo property) { var param = Expression.Parameter(mockType, "m"); From c3c5a7df7b82e66da046a408864934f04c8c9ef0 Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Sat, 1 Jun 2019 11:42:29 +0300 Subject: [PATCH 14/18] Update comment about write only properties --- src/Moq/Interception/InterceptionAspects.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index 5e8212da6..767661fa1 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -351,7 +351,8 @@ public static bool Handle(Invocation invocation, Mock mock) return false; } - // Should ignore write-only properties, as they will be handled by Return aspect. + // Should ignore write-only properties, as they will be handled by Return aspect + // unless if the mock is strict; for those, interception terminates by FailForStrictMock aspect. if (invocationMethod.IsPropertySetter() && !property.CanRead) { return false; From fede8776222199c8118bfc76a6a7d0888b0ddafe Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Sat, 1 Jun 2019 13:07:25 +0300 Subject: [PATCH 15/18] Allow BadSerializable to throw after SetupAllProperties --- src/Moq/Interception/InterceptionAspects.cs | 18 ++---------------- tests/Moq.Tests/MockFixture.cs | 4 ++-- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index 767661fa1..b13ef4884 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -390,22 +390,8 @@ public static bool Handle(Invocation invocation, Mock mock) private static object CreateInitialPropertyValue(Mock mock, MethodInfo getter) { - object initialValue; - Mock innerMock; - try - { - initialValue = mock.GetDefaultValue(getter, out innerMock, - useAlternateProvider: mock.AutoSetupPropertiesDefaultValueProvider); - } - catch - { - // TODO: revisit this catch - // Since this method performs a batch operation, a single failure of the default value - // provider should not tear down the whole operation. The empty default value provider - // is a safe fallback because it does not throw. - initialValue = mock.GetDefaultValue(getter, out innerMock, - useAlternateProvider: DefaultValueProvider.Empty); - } + object initialValue = mock.GetDefaultValue(getter, out Mock innerMock, + useAlternateProvider: mock.AutoSetupPropertiesDefaultValueProvider); if (innerMock != null) { diff --git a/tests/Moq.Tests/MockFixture.cs b/tests/Moq.Tests/MockFixture.cs index 3c8a593bc..d38bda8a5 100644 --- a/tests/Moq.Tests/MockFixture.cs +++ b/tests/Moq.Tests/MockFixture.cs @@ -1250,12 +1250,12 @@ public void Accessing_property_of_bad_serializable_type_throws() } [Fact] - public void Setting_up_property_of_bad_serializable_type_with_SetupAllProperties_does_not_throw() + public void Accessing_property_of_bad_serializable_type_after_SetupAllProperties_throws() { var mock = new Mock() { DefaultValue = DefaultValue.Mock }; mock.SetupAllProperties(); - Assert.Null(mock.Object.BadSerializable); + Assert.ThrowsAny(() => mock.Object.BadSerializable); } #endif } From b2c475946bd02681897f3c7dd3a2f29b1bf50c8a Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Sat, 1 Jun 2019 13:11:00 +0300 Subject: [PATCH 16/18] Cosmetics: revert all the changes in Switches.cs --- src/Moq/Switches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Moq/Switches.cs b/src/Moq/Switches.cs index 4bbee90a9..01cdaedcc 100644 --- a/src/Moq/Switches.cs +++ b/src/Moq/Switches.cs @@ -21,6 +21,6 @@ public enum Switches /// When enabled, specifies that source file information should be collected for each setup. /// This results in more helpful error messages, but may affect performance. /// - CollectDiagnosticFileInfoForSetups = 1 << 0 + CollectDiagnosticFileInfoForSetups = 1 << 0, } } From d2e89114b20736efc64c6b62ec625b3b3228d0df Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Sat, 1 Jun 2019 13:25:23 +0300 Subject: [PATCH 17/18] Add test Property_stubbed_by_SetupAllProperties_should_use_parent_DefaultValue_behaviour_for_inner_mocks --- tests/Moq.Tests/StubExtensionsFixture.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Moq.Tests/StubExtensionsFixture.cs b/tests/Moq.Tests/StubExtensionsFixture.cs index 18bac8772..88846fa2d 100644 --- a/tests/Moq.Tests/StubExtensionsFixture.cs +++ b/tests/Moq.Tests/StubExtensionsFixture.cs @@ -169,6 +169,16 @@ public void Property_stubbed_by_SetupAllProperties_should_capture_DefaultValue_b Assert.NotNull(mock.Object.Hierarchy.Hierarchy); } + + [Fact] + public void Property_stubbed_by_SetupAllProperties_should_use_parent_DefaultValue_behaviour_for_inner_mocks() + { + var mock = new Mock() { DefaultValue = DefaultValue.Mock }; + mock.SetupAllProperties(); + Mock.Get(mock.Object.Hierarchy).DefaultValue = DefaultValue.Empty; + + Assert.NotNull(mock.Object.Hierarchy.Hierarchy); + } public abstract class WriteOnlyProperty { From 4d2e1dd83898bed24062fc8aef01f46bdc6f812b Mon Sep 17 00:00:00 2001 From: Ivan Shimko Date: Sat, 1 Jun 2019 13:35:23 +0300 Subject: [PATCH 18/18] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 877969613..ffd8bbc0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). +## Unreleased + +#### Changed + +* Improved performance for `Mock.Of` and `mock.SetupAllProperties()` as the latter now performs property setups just-in-time, instead of as an ahead-of-time batch operation. (@vanashimko, #826) + + ## 4.11.0 (2019-05-28) Same as 4.11.0-rc2. See changelog entries for the below two pre-release versions.