diff --git a/Source/Extensions.cs b/Source/Extensions.cs
index 588edcbf3..038a584af 100644
--- a/Source/Extensions.cs
+++ b/Source/Extensions.cs
@@ -165,34 +165,6 @@ public static bool IsMockeable(this Type typeToMock)
return typeToMock.GetTypeInfo().IsInterface || typeToMock.GetTypeInfo().IsAbstract || typeToMock.IsDelegate() || (typeToMock.GetTypeInfo().IsClass && !typeToMock.GetTypeInfo().IsSealed);
}
- public static bool IsSerializableMockable(this Type typeToMock)
- {
- return typeToMock.ContainsDeserializationConstructor() && typeToMock.IsGetObjectDataVirtual();
- }
-
- private static bool IsGetObjectDataVirtual(this Type typeToMock)
- {
-#if NETCORE
- return false;
-#else
- var getObjectDataMethod = typeToMock.GetInterfaceMap(typeof (ISerializable)).TargetMethods[0];
- return !getObjectDataMethod.IsPrivate && getObjectDataMethod.IsVirtual && !getObjectDataMethod.IsFinal;
-#endif
- }
-
- private static bool ContainsDeserializationConstructor(this Type typeToMock)
- {
-#if NETCORE
- return false;
-#else
- return typeToMock.GetConstructor(
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
- null,
- new[] {typeof (SerializationInfo), typeof (StreamingContext)},
- null) != null;
-#endif
- }
-
public static bool CanOverride(this MethodBase method)
{
return method.IsVirtual && !method.IsFinal && !method.IsPrivate;
diff --git a/Source/Mock.cs b/Source/Mock.cs
index c3cebc1a1..3bff685a4 100644
--- a/Source/Mock.cs
+++ b/Source/Mock.cs
@@ -746,13 +746,15 @@ private static object GetInitialValue(IDefaultValueProvider valueProvider, Stack
// to deal with loops in the property graph
valueProvider = new EmptyDefaultValueProvider();
}
+#if !NETCORE
else
{
- // to make sure that properties of types that don't impelemt ISerializable properly (Castle throws ArgumentException)
+ // to make sure that properties of types that don't implement ISerializable properly (Castle throws ArgumentException)
// are mocked with default value instead.
// It will only result in exception if the properties are accessed.
valueProvider = new SerializableTypesValueProvider(valueProvider);
}
+#endif
return valueProvider.ProvideDefault(property.GetGetMethod());
}
diff --git a/Source/SerializableTypesValueProvider.cs b/Source/SerializableTypesValueProvider.cs
index b23494669..8a1aaf4e0 100644
--- a/Source/SerializableTypesValueProvider.cs
+++ b/Source/SerializableTypesValueProvider.cs
@@ -1,23 +1,16 @@
-using System.Reflection;
-#if !NETCORE
+#if !NETCORE
+
+using System;
+using System.Reflection;
using System.Runtime.Serialization;
-#endif
namespace Moq
{
-#if !NETCORE
///
/// A that returns an empty default value
/// for serializable types that do not implement properly,
/// and returns the value provided by the decorated provider otherwise.
///
-#else
- ///
- /// A that returns an empty default value
- /// for serializable types that do not implement ISerializable properly,
- /// and returns the value provided by the decorated provider otherwise.
- ///
-#endif
internal class SerializableTypesValueProvider : IDefaultValueProvider
{
private readonly IDefaultValueProvider decorated;
@@ -35,9 +28,33 @@ public void DefineDefault(T value)
public object ProvideDefault(MethodInfo member)
{
- return !member.ReturnType.GetTypeInfo().IsSerializable || member.ReturnType.IsSerializableMockable()
- ? decorated.ProvideDefault(member)
- : emptyDefaultValueProvider.ProvideDefault(member);
+ return IsSerializableWithIncorrectImplementationForISerializable(member.ReturnType)
+ ? emptyDefaultValueProvider.ProvideDefault(member)
+ : decorated.ProvideDefault(member);
+ }
+
+ private static bool IsSerializableWithIncorrectImplementationForISerializable(Type typeToMock)
+ {
+ return typeToMock.IsSerializable
+ && typeof(ISerializable).IsAssignableFrom(typeToMock)
+ && !(ContainsDeserializationConstructor(typeToMock) && IsGetObjectDataVirtual(typeToMock));
+ }
+
+ private static bool ContainsDeserializationConstructor(Type typeToMock)
+ {
+ return typeToMock.GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
+ null,
+ new[] { typeof(SerializationInfo), typeof(StreamingContext) },
+ null) != null;
+ }
+
+ private static bool IsGetObjectDataVirtual(Type typeToMock)
+ {
+ var getObjectDataMethod = typeToMock.GetInterfaceMap(typeof(ISerializable)).TargetMethods[0];
+ return !getObjectDataMethod.IsPrivate && getObjectDataMethod.IsVirtual && !getObjectDataMethod.IsFinal;
}
}
}
+
+#endif
diff --git a/UnitTests/Regressions/IssueReportsFixture.cs b/UnitTests/Regressions/IssueReportsFixture.cs
index 9bd2961e1..b071e32fe 100644
--- a/UnitTests/Regressions/IssueReportsFixture.cs
+++ b/UnitTests/Regressions/IssueReportsFixture.cs
@@ -8,6 +8,7 @@
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
+using Castle.DynamicProxy;
using Moq;
using Moq.Properties;
using Moq.Protected;
@@ -15,6 +16,7 @@
#if !NETCORE
using System.Web.UI.HtmlControls;
+using System.Runtime.Serialization;
#endif
using System.Threading;
using System.Threading.Tasks;
@@ -168,6 +170,160 @@ public void DoTest()
#endif
#endregion
+ #region 163
+
+#if !NETCORE
+ public class Issue163 // see also issue 340 below
+ {
+ [Fact]
+ public void WhenMoqEncountersTypeThatDynamicProxyCannotHandleFallbackToEmptyDefaultValue()
+ {
+ // First, establish that we're looking at situation involving a type that DynamicProxy
+ // cannot handle:
+ var proxyGenerator = new ProxyGenerator();
+ Assert.Throws(() => proxyGenerator.CreateClassProxy());
+
+ // With such a type, Moq should fall back to the empty default value provider:
+ var foo = Mock.Of();
+ Assert.Null(foo.SomeSerializableObject);
+ }
+
+ public abstract class Foo
+ {
+ public abstract NoDeserializationCtor SomeSerializableObject { get; }
+ }
+
+ [Serializable]
+ public abstract class NoDeserializationCtor : ISerializable
+ {
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ ///
+ /// The tests in this class document some of Moq's assumptions about
+ /// how Castle DynamicProxy handles various correct and incorrect
+ /// implementations of `ISerializable`. If any one these tests start
+ /// failing, this is a signal that DynamicProxy has changed, and that
+ /// Moq might have to be adjusted accordingly.
+ ///
+ public class AssumptionsAboutDynamicProxy
+ {
+ [Theory]
+ [InlineData(typeof(CorrectImplementation))]
+ public void CanCreateProxyForCorrectImplementaiton(Type classToProxy)
+ {
+ var proxyGenerator = new ProxyGenerator();
+ var proxy = proxyGenerator.CreateClassProxy(classToProxy);
+ Assert.NotNull(proxy);
+ }
+
+ [Theory]
+ [InlineData(typeof(NoSerializableAttribute))]
+ [InlineData(typeof(NoSerializableAttributeAndNoDeserializationCtor))]
+ [InlineData(typeof(NoSerializableAttributeAndGetObjectDataNotVirtual))]
+ public void DoesNotMindPossibleErrorsIfNoSerializableAttribute(Type classToProxy)
+ {
+ var proxyGenerator = new ProxyGenerator();
+ var proxy = proxyGenerator.CreateClassProxy(classToProxy);
+ Assert.NotNull(proxy);
+ }
+
+ [Theory]
+ [InlineData(typeof(NotISerializable))]
+ [InlineData(typeof(NotISerializableAndNoDeserializationCtor))]
+ [InlineData(typeof(NotISerializableAndGetObjectDataNotVirtual))]
+ public void DoesNotMindPossibleErrorsIfNotISerializable(Type classToProxy)
+ {
+ var proxyGenerator = new ProxyGenerator();
+ var proxy = proxyGenerator.CreateClassProxy(classToProxy);
+ Assert.NotNull(proxy);
+ }
+
+
+ [Theory]
+ [InlineData(typeof(NoDeserializationCtor))]
+ public void DoesMindMissingDeserializationCtor(Type classToProxy)
+ {
+ var proxyGenerator = new ProxyGenerator();
+ Assert.Throws(() => proxyGenerator.CreateClassProxy(classToProxy));
+ }
+
+ [Theory]
+ [InlineData(typeof(GetObjectDataNotVirtual))]
+ public void DoesMindNonVirtualGetObjectData(Type classToProxy)
+ {
+ var proxyGenerator = new ProxyGenerator();
+ Assert.Throws(() => proxyGenerator.CreateClassProxy(classToProxy));
+ }
+
+ public abstract class NoSerializableAttribute : ISerializable
+ {
+ protected NoSerializableAttribute() { }
+ protected NoSerializableAttribute(SerializationInfo info, StreamingContext context) { }
+ public void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ public abstract class NoSerializableAttributeAndNoDeserializationCtor : ISerializable
+ {
+ public void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ public abstract class NoSerializableAttributeAndGetObjectDataNotVirtual : ISerializable
+ {
+ protected NoSerializableAttributeAndGetObjectDataNotVirtual() { }
+ protected NoSerializableAttributeAndGetObjectDataNotVirtual(SerializationInfo info, StreamingContext context) { }
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ [Serializable]
+ public abstract class CorrectImplementation : ISerializable
+ {
+ protected CorrectImplementation() { }
+ protected CorrectImplementation(SerializationInfo info, StreamingContext context) { }
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ [Serializable]
+ public abstract class NotISerializable
+ {
+ protected NotISerializable() { }
+ protected NotISerializable(SerializationInfo info, StreamingContext context) { }
+ public void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ [Serializable]
+ public abstract class NotISerializableAndNoDeserializationCtor
+ {
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ [Serializable]
+ public abstract class NotISerializableAndGetObjectDataNotVirtual
+ {
+ protected NotISerializableAndGetObjectDataNotVirtual() { }
+ protected NotISerializableAndGetObjectDataNotVirtual(SerializationInfo info, StreamingContext context) { }
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ [Serializable]
+ public abstract class NoDeserializationCtor : ISerializable
+ {
+ public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+
+ [Serializable]
+ public abstract class GetObjectDataNotVirtual : ISerializable
+ {
+ protected GetObjectDataNotVirtual() { }
+ protected GetObjectDataNotVirtual(SerializationInfo info, StreamingContext context) { }
+ public void GetObjectData(SerializationInfo info, StreamingContext context) { }
+ }
+ }
+ }
+#endif
+
+ #endregion
+
#region #176
public class Issue176
@@ -478,6 +634,51 @@ public void Strict_mock_expecting_calls_with_nonequal_BadlyHashed_values_should_
#endregion // #328
+ #region 340
+
+#if !NETCORE
+ ///
+ /// These tests check whether the presence of a deserialization ctor and/or a GetObjectData
+ /// method alone can fool Moq into assuming that a type is ISerializable, or implements
+ /// it incompletely when it isn't ISerializable at all.
+ ///
+ public class Issue340 // see also issue 163 above
+ {
+ [Fact]
+ public void ClaimsPrincipal_has_ISerializable_contract_but_is_not_ISerializable()
+ {
+ var ex = Record.Exception(() => Mock.Of());
+ Assert.Null(ex);
+ }
+
+ public abstract class Repro1
+ {
+ public abstract System.Security.Claims.ClaimsPrincipal Principal { get; }
+ }
+
+ [Fact]
+ public void Foo_has_incomplete_ISerializable_contract_but_is_not_ISerializable()
+ {
+ var ex = Record.Exception(() => Mock.Of());
+ Assert.Null(ex);
+ }
+
+ public abstract class Repro2
+ {
+ public abstract Foo FooProperty { get; }
+ }
+
+ [Serializable]
+ public class Foo
+ {
+ public Foo() { }
+ protected Foo(SerializationInfo info, StreamingContext context) { }
+ }
+ }
+#endif
+
+#endregion
+
// Old @ Google Code
#region #47