devlooped / moq

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

Issue with selecting a constructor with null value #969

Closed amir734jj closed 3 years ago

amir734jj commented 4 years ago

I have the following setup and using Moq version 4.13.1. I am not sure this is a bug or not but I am wondering how can I get around this problem and pass null to the constructor argument.

public class Foo
{
    public Foo() { Console.Write("Foo() called"); }

    public Foo(string name, A _, Bar bar): this() { Console.Write("Foo(A) called"); }

    public Foo(string name, B _, Bar bar): this() { Console.Write("Foo(B) called"); }
}

public class A { }

public class B { }

public class Bar { }

class Program
{
    static void Main(string[] args)
    {
        // using default(A) will yield the same error
        var fooMock = new Mock<Foo>("Hello world!", (A) null, new Bar());

        var instance = fooMock.Object;

        Console.WriteLine(instance);
    }
}

I am getting the following error:

Unhandled exception. System.Reflection.AmbiguousMatchException: Ambiguous match found.

Stacktrace:

   at System.DefaultBinder.BindToMethod(BindingFlags bindingAttr, MethodBase[] match, Object[]& args, ParameterModifier[] modifiers, CultureInfo cultureInfo, String[] names, Object& state)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyInstance(Type proxyType, List`1 proxyArguments, Type classToProxy, Object[] constructorArguments)
   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)
   at Moq.Mock`1.InitializeInstance()
   at Moq.Mock`1.OnGetObject()
   at Moq.Mock.get_Object()
   at Moq.Mock`1.get_Object()

Original StackOverFlow question

canton7 commented 4 years ago

Note that this exception is expected. The missed opportunity is if you call:

var fooMock = new Mock<Foo>(() => new Foo("Hello, World", (A)null, new Bar()));

you still get the same exception, even though the constructor to call is known unambiguously.

(A workaround was already posted as an answer on SO)

stakx commented 4 years ago

I agree with @canton7, while there is no way to get this to work if you pass the ctor parameters via a params object[], using the lambda syntax should work. Needs more looking into, but I'll mark this as a bug in the meantime.

amir734jj commented 4 years ago

@stakx Thanks for the follow-up. It would be an awesome feature.

stakx commented 4 years ago

Had a somewhat closer look, this isn't strictly a bug but a current limitation caused by DynamicProxy's API; see https://github.com/castleproject/Core/issues/480.

r-pankevicius commented 4 years ago

@amir734jj I don't understand the real world situation why do you ever need this feature? I.e. using Mock(constructorArgs).

In most use cases I am familiar with all is around Moq Setup. In this situation you can use It.Is(a => a == null) or It.IsIn(null).

stakx commented 4 years ago

@r-pankevicius, this is about a mocked type's constructor, not its regular methods. You cannot set up a constructor; it will get called when instantiating a Mock<T> object.

In other words, you can only create setups when you have first created a Mock<T> instance. Imagine that the mocked type T is a class type that only has parameterized constructors. That means you'll need to provide constructor arguments to e.g. new Mock<T>(...). Only then will you have a mock on which you can setup other methods.

r-pankevicius commented 4 years ago

@stakx OK, I understood. I probably work in a very religious shop that separates interfaces from implementations. So I only have an experience mocking interfaces, not classes. Therefore I did not run into such issue.

stakx commented 3 years ago

Development over at the Castle DynamicProxy repo is a little slow-going these days, and I'm doubting that this issue with DynamicProxy will be fixed anytime soon. In the meantime, there's nothing we can do here, so I am going to close this issue for now... but I'll mark it as "unresolved" so that if and when DynamicProxy offers a way to select a specific constructor to use, we can easily find and reactive this present issue.