Skip to content

Commit

Permalink
Merge pull request #455 from stakx/invisible-internals
Browse files Browse the repository at this point in the history
Make setups for inaccessible internal members fail fast by throwing an exception
  • Loading branch information
stakx authored Sep 23, 2017
2 parents 1475ab2 + 1a75ba5 commit c316449
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1

## Unreleased

#### Changed

* Make setups for inaccessible internal members fail fast by throwing an exception (@stakx, #455)

#### Fixed

* Make `SetupAllProperties` work correctly for same-typed sibling properties (@stakx, #442)
Expand Down
7 changes: 3 additions & 4 deletions Source/Mock.Generic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ namespace Moq
/// <include file='Mock.Generic.xdoc' path='docs/doc[@for="Mock{T}"]/*'/>
public partial class Mock<T> : Mock, IMock<T> where T : class
{
private static IProxyFactory proxyFactory = new CastleProxyFactory();
private static int serialNumberCounter = 0;
private T instance;
private object[] constructorArguments;
Expand Down Expand Up @@ -195,10 +194,10 @@ private void InitializeInstance()
// We're mocking a delegate.
// Firstly, get/create an interface with a method whose signature
// matches that of the delegate.
var delegateInterfaceType = proxyFactory.GetDelegateProxyInterface(typeof(T), out delegateInterfaceMethod);
var delegateInterfaceType = Mock.ProxyFactory.GetDelegateProxyInterface(typeof(T), out delegateInterfaceMethod);
// Then create a proxy for that.
var delegateProxy = proxyFactory.CreateProxy(
var delegateProxy = Mock.ProxyFactory.CreateProxy(
delegateInterfaceType,
this.Interceptor,
this.ImplementedInterfaces.ToArray(),
Expand All @@ -210,7 +209,7 @@ private void InitializeInstance()
}
else
{
this.instance = (T)proxyFactory.CreateProxy(
this.instance = (T)Mock.ProxyFactory.CreateProxy(
typeof(T),
this.Interceptor,
this.ImplementedInterfaces.Skip(this.InternallyImplementedInterfaceCount - 1).ToArray(),
Expand Down
19 changes: 19 additions & 0 deletions Source/Mock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ namespace Moq
/// <include file='Mock.xdoc' path='docs/doc[@for="Mock"]/*'/>
public abstract partial class Mock : IFluentInterface
{
internal static IProxyFactory ProxyFactory => CastleProxyFactory.Instance;

private bool isInitialized;
private DefaultValue defaultValue = DefaultValue.Empty;
private IDefaultValueProvider defaultValueProvider = new EmptyDefaultValueProvider();
Expand Down Expand Up @@ -434,6 +436,7 @@ internal static MethodCall<T> Setup<T>(Mock<T> mock, Expression<Action<T>> expre
var args = methodCall.Arguments.ToArray();
ThrowIfSetupExpressionInvolvesUnsupportedMember(expression, method);
ThrowIfSetupMethodNotVisibleToProxyFactory(method);
var call = new MethodCall<T>(mock, condition, expression, method, args);
var targetInterceptor = GetInterceptor(methodCall.Object, mock);
Expand Down Expand Up @@ -462,6 +465,7 @@ internal static MethodCallReturn<T, TResult> Setup<T, TResult>(
var args = methodCall.Arguments.ToArray();
ThrowIfSetupExpressionInvolvesUnsupportedMember(expression, method);
ThrowIfSetupMethodNotVisibleToProxyFactory(method);
var call = new MethodCallReturn<T, TResult>(mock, condition, expression, method, args);
var targetInterceptor = GetInterceptor(methodCall.Object, mock);
Expand Down Expand Up @@ -491,6 +495,7 @@ internal static MethodCallReturn<T, TProperty> SetupGet<T, TProperty>(
var propGet = prop.GetGetMethod(true);
ThrowIfSetupExpressionInvolvesUnsupportedMember(expression, propGet);
ThrowIfSetupMethodNotVisibleToProxyFactory(propGet);
var call = new MethodCallReturn<T, TProperty>(mock, condition, expression, propGet, new Expression[0]);
// Directly casting to MemberExpression is fine as ToPropertyInfo would throw if it wasn't
Expand Down Expand Up @@ -549,6 +554,7 @@ internal static SetterMethodCall<T, TProperty> SetupSet<T, TProperty>(

var propSet = prop.GetSetMethod(true);
ThrowIfSetupExpressionInvolvesUnsupportedMember(expression, propSet);
ThrowIfSetupMethodNotVisibleToProxyFactory(propSet);

var call = new SetterMethodCall<T, TProperty>(mock, expression, propSet);
var targetInterceptor = GetInterceptor(((MemberExpression)expression.Body).Expression, mock);
Expand Down Expand Up @@ -800,6 +806,19 @@ private static void ThrowIfPropertyNotReadable(PropertyInfo prop)
}
}

private static void ThrowIfSetupMethodNotVisibleToProxyFactory(MethodInfo method)
{
if (Mock.ProxyFactory.IsMethodVisible(method, out string messageIfNotVisible) == false)
{
throw new ArgumentException(string.Format(
CultureInfo.CurrentCulture,
Resources.MethodNotVisibleToProxyFactory,
method.DeclaringType.Name,
method.Name,
messageIfNotVisible));
}
}

private static void ThrowIfSetupExpressionInvolvesUnsupportedMember(Expression setup, MethodInfo method)
{
if (method.IsStatic)
Expand Down
14 changes: 12 additions & 2 deletions Source/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Source/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,8 @@ Expected invocation on the mock once, but was {4} times: {1}</value>
<data name="UseItExprIsNullRatherThanNullArgumentValue" xml:space="preserve">
<value>Use ItExpr.IsNull&lt;TValue&gt; rather than a null argument value, as it prevents proper method lookup.</value>
</data>
<data name="MethodNotVisibleToProxyFactory" xml:space="preserve">
<value>Cannot set up {0}.{1} because it is not accessible to the proxy generator used by Moq:
{2}</value>
</data>
</root>
7 changes: 7 additions & 0 deletions Source/Proxy/CastleProxyFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ namespace Moq.Proxy
{
internal class CastleProxyFactory : IProxyFactory
{
public static CastleProxyFactory Instance { get; } = new CastleProxyFactory();

private static readonly ProxyGenerator generator = CreateProxyGenerator();

[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "By Design")]
Expand Down Expand Up @@ -104,6 +106,11 @@ public object CreateProxy(Type mockType, ICallInterceptor interceptor, Type[] in
}
}

public bool IsMethodVisible(MethodInfo method, out string messageIfNotVisible)
{
return ProxyUtil.IsAccessible(method, out messageIfNotVisible);
}

private static readonly Dictionary<Type, Type> delegateInterfaceCache = new Dictionary<Type, Type>();
private static readonly ProxyGenerationOptions proxyOptions;
private static int delegateInterfaceSuffix;
Expand Down
2 changes: 2 additions & 0 deletions Source/Proxy/IProxyFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ internal interface IProxyFactory
{
object CreateProxy(Type mockType, ICallInterceptor interceptor, Type[] interfaces, object[] arguments);

bool IsMethodVisible(MethodInfo method, out string messageIfNotVisible);

/// <summary>
/// Gets an autogenerated interface with a method on it that matches the signature of the specified
/// <paramref name="delegateType"/>.
Expand Down
41 changes: 41 additions & 0 deletions UnitTests/MockFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,5 +1084,46 @@ public interface INewFoo : IFoo
public interface INewBar : IBar
{
}

// Note that this test requires that there be no [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
// or similar defined in this test assembly. If some other test requires that internals be made
// visible to DynamicProxy, then this test must be disabled.
[Fact]
public void SetupOnInaccessibleMethodThrowsException()
{
var mock = new Mock<Accessibility.ClassWithAccessibleAndInaccessibleMethod>();

var error = Record.Exception(() =>
{
mock.Setup(m => m.Internal());
});

Assert.NotNull(error);
Assert.IsType<ArgumentException>(error);
Assert.Contains("accessible", error.Message);
Assert.Contains("proxy generator", error.Message);
}

[Fact]
public void SetupOnAccessibleMethodDoesNotThrowException()
{
var mock = new Mock<Accessibility.ClassWithAccessibleAndInaccessibleMethod>();

var error = Record.Exception(() =>
{
mock.Setup(m => m.Public());
});

Assert.Null(error);
}

public class Accessibility
{
public class ClassWithAccessibleAndInaccessibleMethod
{
public virtual void Public() => throw new InvalidOperationException("Public");
internal virtual void Internal() => throw new InvalidOperationException("Internal");
}
}
}
}

0 comments on commit c316449

Please sign in to comment.