devlooped / moq

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

Cannot mock subclass of superclass with internal virtual methods #991

Open GeeWee opened 4 years ago

GeeWee commented 4 years ago

Unfortunately, this bug report is a little confusing, as I have tried multiple times to create a small reproducible example, but I am unable to.

I'm trying to make a mock of the Azure BlockBlobClient This works fine, I can easily execute

var clientMock = new Mock<BlockBlobClient>();
var obj = clientMock.Object;

However, if I subclass the BlockBlobClient, everything goes wrong, even if the subclass does nothing.

[Fact]
public void ShouldBeAbleToSubclass()
{
    // Arrange
    var clientMock = new Mock<TestBlockBlobClient>();
    var obj = clientMock.Object;
}

}

public class TestBlockBlobClient : BlockBlobClient
{
}

I then get the following error:

  Error Message:
   System.ArgumentException : Type to mock must be an interface, a delegate, or a non-sealed, non-static class.
---- System.TypeLoadException : Method 'get_Pipeline' on type 'Castle.Proxies.TestBlockBlobClientProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is overriding a method that is not visible from that assembly.
  Stack Trace:
     at Moq.CastleProxyFactory.CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, Object[] arguments)
   at Moq.Mock`1.InitializeInstance()
   at Moq.Mock`1.OnGetObject()
   at Moq.Mock.get_Object()
   at Moq.Mock`1.get_Object()
   at AIP.Shared.Test.BlockBlobClientTests.ShouldBeAbleToSubclass() in /home/geewee/programming/OAI/Shared.Test/BlockBlobClientTests.cs:line 91
----- Inner Stack Trace -----
   at System.Reflection.Emit.TypeBuilder.CreateTypeNoLock()
   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.ClassProxyGenerator.GenerateType(String name, Type[] interfaces, INamingScope namingScope)
   at Castle.DynamicProxy.Generators.ClassProxyGenerator.<>c__DisplayClass1_0.<GenerateCode>b__0(String n, INamingScope s)
   at Castle.DynamicProxy.Generators.BaseProxyGenerator.<>c__DisplayClass33_0.<ObtainProxyType>b__0(CacheKey _)
   at Castle.Core.Internal.SynchronizedDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Castle.DynamicProxy.Generators.BaseProxyGenerator.ObtainProxyType(CacheKey cacheKey, Func`3 factory)
   at Castle.DynamicProxy.Generators.ClassProxyGenerator.GenerateCode(Type[] interfaces, ProxyGenerationOptions options)
   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)

It seems like Moq is trying to mock the internal virtual method Pipeline in the BlobBaseClient, which the BlockBlobClient inherits from.

Seeing as the method is internal and in another assembly, I cannot override it. However if I define a property with the same name, ala:

public class TestBlockBlobClient : BlockBlobClient
{
    public HttpPipeline Pipeline => null!;
}

I get the same error, but with the next internal virtual property

 System.TypeLoadException : Method 'get_Version' on type 'Castle.Proxies.TestBlockBlobClientProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is overriding a method that is not visible from that assembly.

which is also a property on the superclass

So my theory is that there's something that's going on with the internal virtual properties, but only in a subclass of a subclass in another assembly.

Unfortunately, I cannot reproduce the problem, if I try to mimick the same thing locally (e.g. create two assemblies and the same internal virtual structure). I'm not sure if this is a Windsor or a Moq problem either.

Back this issue Back this issue

stakx commented 4 years ago

Interesting.

I can't tell you right away why that happens, but I can tell you that the problem happens in the infrastructure below Moq, not in Moq itself. I've opened a bug report over at the DynamicProxy repository (https://github.com/castleproject/Core/issues/488). I'll close your issue here, since there's nothing we can do in Moq.

P.S.: Some more info in the Castle.Core issue that might be useful to you.

stakx commented 4 years ago

I closed this too early... sorry. Let's close this once we've updated to a new version of DynamicProxy that contains a fix for this.

GeeWee commented 4 years ago

Thanks for the quick response! I see now why I couldn't reproduce it locally, as it was a little more complicated than I gave it credit for.

smokedlinq commented 3 years ago

Ran across this today, did anyone find a way around this or did they have to redesign the clients that are inheriting from those types of base classes?

thackman7 commented 1 year ago

I'm running in to the same problem trying to create a mock for a named subclass of Azure DataLakeServiceClient.

Inner Exception 1: TypeLoadException: Method 'get_ClientConfiguration' on type 'Castle.Proxies.MyAppClientProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is overriding a method that is not visible from that assembly.

Identekit commented 1 year ago

I just ran into this same issue with one of my own classes.

System.TypeLoadException: Method 'MyMethod' on type 'Castle.Proxies.MyClassProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is overriding a method that is not visible from that assembly.

This is in a .NET 6 project...I might not have made internals visible in the correct way. There's no Assembly.cs file anymore and I couldn't find much info on how to do this in .NET 6 except for the following (which may not be correct)

MyProject.csproj

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
    <_Parameter1>MyProject.Test</_Parameter1>       
  </AssemblyAttribute>
</ItemGroup>

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
    <_Parameter1>InternalsVisible.DynamicProxyGenAssembly2</_Parameter1>        
  </AssemblyAttribute>
</ItemGroup>

UPDATE Turns out the problem I was having was in "InternalsVisible.DynamicProxyGenAssembly2". Once I removed the "InternalsVisible." part everything worked.

MyProject.csproj

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
    <_Parameter1>MyProject.Test</_Parameter1>       
  </AssemblyAttribute>
</ItemGroup>

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
    <_Parameter1>DynamicProxyGenAssembly2</_Parameter1>     
  </AssemblyAttribute>
</ItemGroup>

I also found that as of .NET 5 and higher, the following also works (source: here):

MyProject.csproj

  <ItemGroup>
    <InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
  </ItemGroup>

  <ItemGroup>
    <InternalsVisibleTo Include="MyProject.Test" />
  </ItemGroup>
gorillapower commented 1 year ago

I have had some serious code rage with this issue. Any workarounds for when you need to create a subclass like OP has? My issue is when I try create a subclass of BlobContainerClient

mrobinson-campion commented 1 year ago

I'm getting the same issue when I try create a subclass of BlobServiceClient

github-actions[bot] commented 2 months ago

Due to lack of recent activity, this issue has been labeled as 'stale'. It will be closed if no further activity occurs within 30 more days. Any new comment will remove the label.