devlooped / moq

The most popular and friendly mocking framework for .NET
Other
5.88k stars 800 forks source link

Constructing Mock<DerivedControl>.Object throws TypeLoadException #1488

Open andrewimcclement opened 2 months ago

andrewimcclement commented 2 months ago

Describe the Bug

Given an internal class DerivedControl inheriting from System.Windows.Forms.Control, constructing new Mock<DerivedControl>().Object throws ArgumentException, wrapping TypeLoadException. I cannot reproduce this issue without using System.WIndows.Forms.Control.

I am not certain if this is a bug in Castle.Core, but thought I would ask here first.

Steps to Reproduce

Full code with additional examples can be found at https://github.com/andrewimcclement/MoqInternalsVisibleToIssue.

This test (in Production.Tests.csproj) passes on .NET Framework 4.8 but fails on .NET 6.0 & .NET 8.0.

using NUnit.Framework;
using Moq;

namespace Production.Tests;

public class DerivedControl : Control {}

internal class InternalsVisibleToTests
{
    [Test]
    public void FailsOnNetCore()
    {
        var obj = new Mock<DerivedControl>();
        Assert.That(() => obj.Object, Throws.Nothing);
    }
}

Expected Behavior

Given this passes on .NET Framework 4.8, I would expect this to pass (or understand why System.Windows.Forms.Control is special

Exception with Stack Trace

.NET 8.0 stack trace: (.NET 6.0 stack trace is similar)

System.ArgumentException : Type to mock (Production.InternalDerivedControl) must be an interface, a delegate, or a non-sealed, non-static class.
  ----> System.TypeLoadException : Method 'NotifyValidationResult' on type 'Castle.Proxies.InternalDerivedControlProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is overriding a method that is not visible from that assembly.
   at Moq.CastleProxyFactory.CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, Object[] arguments) in /_/src/Moq/Interception/CastleProxyFactory.cs:line 114
   at Moq.Mock`1.InitializeInstance() in /_/src/Moq/Mock`1.cs:line 502
   at Moq.Mock`1.OnGetObject() in /_/src/Moq/Mock`1.cs:line 516
   at Moq.Mock.get_Object() in /_/src/Moq/Mock.cs:line 180
   at Moq.Mock`1.get_Object() in /_/src/Moq/Mock`1.cs:line 453
   at Production.Tests.InternalsVisibleToTests.FailsOnNetCore() in C:\Git\github\andrewimcclement\MoqInternalsVisibleToIssue\Production.Tests\UnitTest1.cs:line 21
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
--TypeLoadException
   at System.Reflection.Emit.RuntimeTypeBuilder.CreateTypeNoLock()
   at System.Reflection.Emit.RuntimeTypeBuilder.CreateTypeInfoImpl()
   at System.Reflection.Emit.TypeBuilder.CreateTypeInfo()
   at Castle.DynamicProxy.Generators.Emitters.AbstractTypeEmitter.CreateType(TypeBuilder type)
   at Castle.DynamicProxy.Generators.Emitters.AbstractTypeEmitter.BuildType()
   at Castle.DynamicProxy.Generators.BaseClassProxyGenerator.GenerateType(String name, INamingScope namingScope)
   at Castle.DynamicProxy.Generators.BaseProxyGenerator.<>c__DisplayClass13_0.<GetProxyType>b__0(CacheKey cacheKey)
   at Castle.Core.Internal.SynchronizedDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Castle.DynamicProxy.Generators.BaseProxyGenerator.GetProxyType()
   at Castle.DynamicProxy.DefaultProxyBuilder.CreateClassProxyType(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
   at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyType(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
   at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors)
   at Moq.CastleProxyFactory.CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, Object[] arguments) in /_/src/Moq/Interception/CastleProxyFactory.cs:line 110

Note that InternalDerivedControl is a non-sealed, non-static class, so even if this behaviour is correct, the error message seems inaccurate. Also, looking at the source code of System.Windows.Forms.Control, I cannot see any relevant change to internal virtual void NotifyValidationResult

Version Info

Moq 4.20.70 used (implicitly using Castle.Core 5.1.1).

Additional information

I hit this issue while trying to upgrade some test projects from .NET Framework to .NET 8.

Back this issue Back this issue

kzu commented 1 month ago

Have you set the <UseWindowsForms>true</UseWindowsForms> when using .NET6/8 as documented?

andrewimcclement commented 1 month ago

Yes - see https://github.com/andrewimcclement/MoqInternalsVisibleToIssue/blob/cd273f348e2edf7ab301dfa452fece060c85bff8/Production/Production.csproj#L8 and https://github.com/andrewimcclement/MoqInternalsVisibleToIssue/blob/cd273f348e2edf7ab301dfa452fece060c85bff8/Production.Tests/Production.Tests.csproj#L8.