Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to mock interfaces that consume value types by reference #378

Closed
TAGC opened this issue Mar 12, 2018 · 8 comments
Closed

Unable to mock interfaces that consume value types by reference #378

TAGC opened this issue Mar 12, 2018 · 8 comments
Milestone

Comments

@TAGC
Copy link

TAGC commented Mar 12, 2018

C# 7.2 introduced the possibility of using reference semantics for value types - this is done by applying the in modifier for value-type parameters in method signatures.

NSubstitute is apparently not able to generate mocks for interfaces that use these new modifiers. Given the unit tests below:

using NSubstitute;
using Xunit;

namespace SomeProject.Tests
{
    public readonly struct Struct
    {
    }

    public interface IStructByRefConsumer
    {
        void Consume(in Struct message);
    }

    public interface IStructByValueConsumer
    {
        void Consume(Struct message);
    }

    public class TempSpec
    {
        // Fails.
        [Fact]
        internal void IStructByRefConsumer_Test()
        {
            _ = Substitute.For<IStructByRefConsumer>();
        }

        // Passes.
        [Fact]
        internal void IStructByValueConsumer_Test()
        {
            _ = Substitute.For<IStructByValueConsumer>();
        }
    }
}

What I find is that IStructByValueConsumer_Test passes as expected, while IStructByRefConsumer_Test fails with this exception:

Test Name:	SomeProject.Tests.TempSpec.IStructByRefConsumer_Test
Test FullName:	SomeProject.Tests.TempSpec.IStructByRefConsumer_Test
Test Source:	C:\path\to\tests\TempSpec.cs : line 30
Test Outcome:	Failed
Test Duration:	0:00:22.187

Result StackTrace:	
at System.Reflection.Emit.TypeBuilder.TermCreateClass(RuntimeModule module, Int32 tk, ObjectHandleOnStack type)
   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.InterfaceProxyWithoutTargetGenerator.GenerateType(String typeName, Type proxyTargetType, Type[] interfaces, INamingScope namingScope)
   at Castle.DynamicProxy.Generators.InterfaceProxyWithTargetGenerator.<>c__DisplayClass6_0.<GenerateCode>b__0(String n, INamingScope s)
   at Castle.DynamicProxy.Generators.BaseProxyGenerator.ObtainProxyType(CacheKey cacheKey, Func`3 factory)
   at Castle.DynamicProxy.ProxyGenerator.CreateInterfaceProxyWithoutTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, IInterceptor[] interceptors)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments)
   at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments, SubstituteConfig config)
   at NSubstitute.Substitute.For[T](Object[] constructorArguments)
   at SomeProject.Tests.TempSpec.IStructByRefConsumer_Test() in C:\path\to\tests\TempSpec.cs:line 32
Result Message:	System.TypeLoadException : Signature of the body and declaration in a method implementation do not match.  Type: 'Castle.Proxies.IStructByRefConsumerProxy'.  Assembly: 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
@TAGC
Copy link
Author

TAGC commented Mar 12, 2018

This isn't an NSubstitute-specific problem. I found the same problem with Moq too:

public class TempSpec
{
    // Fails.
    [Fact]
    internal void Struct_ByRef_Moq_Test()
    {
        _ = Mock.Of<IStructByRefConsumer>();
    }

    // Fails.
    [Fact]
    internal void Struct_ByRef_NSubstitute_Test()
    {
        _ = Substitute.For<IStructByRefConsumer>();
    }

    // Passes.
    [Fact]
    internal void Struct_ByValue_Moq_Test()
    {
        _ = Mock.Of<IStructByValueConsumer>();
    }

    // Passes.
    [Fact]
    internal void Struct_ByValue_NSubstitute_Test()
    {
        _ = Substitute.For<IStructByValueConsumer>();
    }
}
Test Name:	SomeProject.Tests.TempSpec.Struct_ByRef_Moq_Test
Test FullName:	SomeProject.Tests.TempSpec.Struct_ByRef_Moq_Test
Test Source:	 : line -1
Test Outcome:	Failed
Test Duration:	0:00:00.016

Result StackTrace:	
at System.Reflection.Emit.TypeBuilder.TermCreateClass(RuntimeModule module, Int32 tk, ObjectHandleOnStack type)
   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.InterfaceProxyWithoutTargetGenerator.GenerateType(String typeName, Type proxyTargetType, Type[] interfaces, INamingScope namingScope)
   at Castle.DynamicProxy.Generators.InterfaceProxyWithTargetGenerator.<>c__DisplayClass6_0.<GenerateCode>b__0(String n, INamingScope s)
   at Castle.DynamicProxy.Generators.BaseProxyGenerator.ObtainProxyType(CacheKey cacheKey, Func`3 factory)
   at Castle.DynamicProxy.ProxyGenerator.CreateInterfaceProxyWithoutTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, IInterceptor[] interceptors)
   at Moq.Mock`1.InitializeInstancePexProtected()
   at Moq.Mock`1.OnGetObject()
   at Moq.Mock`1.get_Object()
   at Moq.Mocks.<CreateMocks>d__6`1.MoveNext()
   at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at System.Linq.EnumerableExecutor`1.Execute()
   at System.Linq.EnumerableQuery`1.System.Linq.IQueryProvider.Execute[TElement](Expression expression)
   at System.Linq.Queryable.First[TSource](IQueryable`1 source)
   at System.Linq.Queryable.First[TSource](IQueryable`1 source)
   at SomeProject.Tests.TempSpec.Struct_ByRef_Moq_Test() in C:\path\to\tests\TempSpec.cs:line 33
Result Message:	System.TypeLoadException : Signature of the body and declaration in a method implementation do not match.  Type: 'Castle.Proxies.IStructByRefConsumerProxy'.  Assembly: 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.

Both of these fail at:

Castle.DynamicProxy.ProxyGenerator.CreateInterfaceProxyWithoutTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, IInterceptor[] interceptors)

So I guess the bug is in Castle.Core.

@zvirja
Copy link
Contributor

zvirja commented Mar 12, 2018

@TAGC Thanks for firing the issue for Castle. It's indeed their issue 😉

@TAGC TAGC closed this as completed Mar 12, 2018
@TAGC
Copy link
Author

TAGC commented Mar 13, 2018

Just to let you guys know, the Castle.Core team are considering bumping their library to (at least) .NET Standard 1.5 in order to resolve this proxy generation issue. The Moq guys are prepared to do something similar when the Castle.Core team release an updated build, so you may want to consider following suit.

@zvirja
Copy link
Contributor

zvirja commented Mar 13, 2018

Thanks a lot for the update! Let's re-open this issue to track the process.

Just to let you guys know, the Castle.Core team are considering bumping their library to (at least) .NET Standard 1.5

That should not be a big problem, as we are working on the major version now, so it's a good time to make this change.

@zvirja zvirja reopened this Mar 13, 2018
@zvirja
Copy link
Contributor

zvirja commented Mar 15, 2018

Required list of actions:

  • Wait till Castle.Core fixes the issue and releases a new version.
  • Consume new version of Castle.Core dependency, increasing the version of .NET Standard if needed.
  • Improve our delegate generator to copy custom modifiers as well when API is available (.NET Standard 1.5+, full .NET Framework)

@dtchepak dtchepak added this to the v4.0 milestone Apr 19, 2018
dtchepak added a commit to dtchepak/NSubstitute that referenced this issue Jun 7, 2018
dtchepak added a commit to dtchepak/NSubstitute that referenced this issue Jun 7, 2018
@Netzeband
Copy link

Netzeband commented Jun 28, 2020

Hello

Today I noticed the same issue again with a different setup:

Minimal example:

using NSubstitute;
using NUnit.Framework;

namespace DebugNUnitError
{
    [TestFixture]
    public class Class1
    {
        public interface IBar {}
        
        public interface IFoo<T>
        {
            bool DoSomething(in string[] stringArray);
        }
        
        [Test]
        public void TestSubstituteFor()
        {
            var mock = Substitute.For<IFoo<IBar>>();
        }
    }
}

Library and runtime versions:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <RootNamespace>DebugError</RootNamespace>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
      <PackageReference Include="NSubstitute" Version="4.2.2" />
      <PackageReference Include="NUnit" Version="3.12.0" />
      <PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
    </ItemGroup>

</Project>

As far as I understood the issue should be fixed since Core 2.2.2 and in the current version of NSubstitute. But I still get this error:

Castle.DynamicProxy.ProxyGenerationException : There was an error in static constructor on type Castle.Proxies.ObjectProxy. This is likely a bug in DynamicProxy. Please report it.
  ----> System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
  ----> System.TypeInitializationException : The type initializer for 'Castle.Proxies.ObjectProxy' threw an exception.
  ----> System.MissingMethodException : Method not found: 'Boolean Common.Network.DataProcessor.IRegister`1.TryGet(System.String[] ByRef, !0 ByRef, Int32 ByRef)'.
   at Castle.DynamicProxy.Internal.TypeUtil.SetStaticField(Type type, String fieldName, BindingFlags additionalFlags, Object value)
   at Castle.DynamicProxy.Generators.BaseProxyGenerator.InitializeStaticFields(Type builtType)
   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 NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.CreateProxyUsingCastleProxyGenerator(Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments, IInterceptor[] interceptors, ProxyGenerationOptions proxyGenerationOptions)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateTypeProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments)
   at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments, Boolean callBaseByDefault)
   at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments)
   at NSubstitute.Substitute.For(Type[] typesToProxy, Object[] constructorArguments)
   at NSubstitute.Substitute.For[T](Object[] constructorArguments)
   at Common.Network.DataProcessor.TestRegistry.SetUp() in D:\Projects\CSharp\projects\hello.game\source\HelloWorldGame\Common\Network\Network.unit_tests\DataProcessor\TestRegistry.cs:line 14
--TargetInvocationException
   at System.RuntimeFieldHandle.SetValue(RtFieldInfo field, Object obj, Object value, RuntimeType fieldType, FieldAttributes fieldAttr, RuntimeType declaringType, Boolean& domainInitialized)
   at System.Reflection.RtFieldInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture)
   at System.Reflection.FieldInfo.SetValue(Object obj, Object value)
   at Castle.DynamicProxy.Internal.TypeUtil.SetStaticField(Type type, String fieldName, BindingFlags additionalFlags, Object value)
--TypeInitializationException

--MissingMethodException
   at Castle.Proxies.ObjectProxy..cctor()

Further information:

  • The issue disappears, if I don't use the in modifier.
  • It also disappears, when I don't use the generic argument for interface IFoo.

@dtchepak
Copy link
Member

@Netzeband I think this is related to castleproject/Core#430. It still seems it is not fixed in current CoreCLR versions (castleproject/Core#430 (comment)).

@Netzeband
Copy link

@dtchepak Thanks for the hint. This seems to be the same issue. So I will watch the other ticket to see, if it got fixed or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants