Skip to content

Commit

Permalink
Fix mocking for redeclared interface properties
Browse files Browse the repository at this point in the history
Commit 97a0f14 changed the behavior of `mock.SetupAllProperties` so
that read-only properties could be mocked, too. At the same time, it
broke the frequent use case where a read-only property in a base
interface is redeclared as a read-write property in a derived inter-
face. Those redeclared properties would no longer be mocked correctly.

This commit reenables that scenario simply by changing the order in
which properties are set up: properties in base interfaces (i.e. those
furthest down the ancestor line) get set up before properties in more
derived interfaces (i.e. those closer to the mocked type).
  • Loading branch information
stakx committed Jun 20, 2017
1 parent 520dd92 commit 5d68d05
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 3 deletions.
51 changes: 51 additions & 0 deletions Source/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -323,5 +324,55 @@ private static MethodInfo GetInvokeMethodFromUntypedDelegateCallback(Delegate ca
return null;
}
}

/// <summary>
/// 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.
/// </summary>
/// <param name="type">The type whose properties are to be returned.</param>
internal static List<PropertyInfo> GetAllPropertiesInDepthFirstOrder(this Type type)
{
var properties = new List<PropertyInfo>();
var none = new HashSet<Type>();

type.AddPropertiesInDepthFirstOrderTo(properties, typesAlreadyVisited: none);

return properties;
}

/// <summary>
/// This is a helper method supporting <see cref="GetAllPropertiesInDepthFirstOrder(Type)"/>
/// and is not supposed to be called directly.
/// </summary>
private static void AddPropertiesInDepthFirstOrderTo(this Type type, List<PropertyInfo> properties, HashSet<Type> 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.GetTypeInfo().BaseType != null)
//{
// type.GetTypeInfo().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);
}
}
}
}
}
15 changes: 12 additions & 3 deletions Source/Mock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -691,12 +691,21 @@ private static void SetupAllProperties(Mock mock, Stack<Type> mockedTypesStack)
{
var mockType = mock.MockedType;
mockedTypesStack.Push(mockType);
var properties = mockType.GetProperties()
.Concat(mockType.GetInterfaces().SelectMany(i => i.GetProperties()))

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.GetIndexParameters().Length == 0 &&
!(p.CanWrite ^ (p.CanWrite & p.CanOverrideSet())))
p.CanWrite == p.CanOverrideSet())
// ^ The last 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.
.Distinct();

var setupPropertyMethod = mock.GetType().GetMethods()
Expand Down

0 comments on commit 5d68d05

Please sign in to comment.