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

AutoMock.Mock<T> doesn't use constructor with parameters #42

Open
msft-mw opened this issue Apr 10, 2021 · 4 comments
Open

AutoMock.Mock<T> doesn't use constructor with parameters #42

msft-mw opened this issue Apr 10, 2021 · 4 comments

Comments

@msft-mw
Copy link

msft-mw commented Apr 10, 2021

Describe the Bug

Parameters passed when getting a mock from an AutoMock aren't used and instead mocking fails trying to find a parameter-less constructor.

Steps to Reproduce

public abstract class AbstractType
{
    public int Param { get; }

    protected AbstractType(int param)
    {
        this.Param = param;
    }

    public abstract int DoThing();
}

[TestClass]
public class Repro
{
    [TestMethod]
    public void ParametersTest()
    {
        using (var mocks = AutoMock.GetLoose())
        {
            Mock<AbstractType> mock = mocks.Mock<AbstractType>(new TypedParameter(typeof(int), 8));
            mock.Setup(q => q.DoThing()).Returns(16);
        }
    }
}

Expected Behavior

I expected the TypedParameter to be used so that the mock is constructed. I recognize that the point of the mock is to ignore the value I provided, which is fine, and that's what I'm using the Setup for, but the inability to create the mock in the first place is what is confusing to me. I can manually mock the type using

var mock = new Mock<AbstractType>(8);

And it will succeed, and I can then put the mock inside the AutoMock to later resolve. It's very possible that this isn't supposed to work and I'm not understanding the documentation!

Exception with Stack Trace

Error Message:
   Test method test.Repro.ParametersTest threw exception:
Autofac.Core.DependencyResolutionException: An exception was thrown while activating λ:System.Object. ---> Castle.DynamicProxy.InvalidProxyConstructorArgumentsException: Can not instantiate proxy of class: src.AbstractType.
Could not find a parameterless constructor.
  Stack Trace:
      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.Proxy.CastleProxyFactory.CreateProxy(Type mockType, ICallInterceptor interceptor, Type[] interfaces, Object[] arguments)
   at Moq.Mock`1.<InitializeInstance>b__19_0()
   at Moq.PexProtector.Invoke(Action action)
   at Moq.Mock`1.InitializeInstance()
   at Moq.Mock`1.OnGetObject()
   at Moq.Mock.GetObject()
   at Moq.Mock.get_Object()
   at Autofac.Extras.Moq.MoqRegistrationHandler.CreateMock(IComponentContext context, TypedService typedService)
   at Autofac.Extras.Moq.MoqRegistrationHandler.<>c__DisplayClass6_0.<RegistrationsFor>b__0(IComponentContext c, IEnumerable`1 p)
   at Autofac.Builder.RegistrationBuilder.<>c__DisplayClass0_0`1.<ForDelegate>b__0(IComponentContext c, IEnumerable`1 p)
   at Autofac.Core.Activators.Delegate.DelegateActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
   at Autofac.Core.Activators.Delegate.DelegateActivator.<ConfigurePipeline>b__2_0(ResolveRequestContext ctxt, Action`1 next)
   at Autofac.Core.Resolving.Middleware.DelegateMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
--- End of inner exception stack trace ---
    at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Pipeline.ResolvePipeline.Invoke(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Middleware.RegistrationPipelineInvokeMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Middleware.SharingMiddleware.<>c__DisplayClass5_0.<Execute>b__0()
   at Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(Guid id, Func`1 creator)
   at Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(Guid primaryId, Nullable`1 qualifyingId, Func`1 creator)
   at Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Pipeline.ResolvePipeline.Invoke(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
   at Autofac.Core.Resolving.ResolveOperation.ExecuteOperation(ResolveRequest request)
   at Autofac.Core.Resolving.ResolveOperation.Execute(ResolveRequest request)
   at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest request)
   at Autofac.Core.Container.ResolveComponent(ResolveRequest request)
   at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
   at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
   at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
   at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters)
   at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, Parameter[] parameters)
   at Autofac.Extras.Moq.AutoMock.Create[T](Boolean isMock, Parameter[] parameters)
   at Autofac.Extras.Moq.AutoMock.Mock[T](Parameter[] parameters)
   at test.Repro.ParametersTest() in /mnt/c/source/AfRepro/test/Repro.cs:line 24

Dependency Versions

Autofac: 6.0.0
Autofac.Extras.Moq: 6.0.0
Microsoft.NET.Test.Sdk: 16.9.4
MSTest.TestAdapter: 2.2.3
MSTest.TestFramework: 2.2.3
coverlet.collector: 3.0.3

Additional Info

@tillig
Copy link
Member

tillig commented Apr 10, 2021

Sorry it seems this isn't working. I'm honestly not sure exactly what's up off hand but wanted to get back to you in a timely fashion to say... Likely we won't be getting to look at this in a timely fashion. Any help you can provide up front to step into the code with Source Link (there's not much code in this library) and pinpoint the issue... or even get a PR together... will go a long way. There are an unfortunately low number of folks available at the moment to troubleshoot stuff, and most of the time we have is dedicated to core Autofac and more frequently used extensions and doc. I wish that wasn't the case, but it kind of is what it is at the moment. I just didn't want you sitting waiting for a solution that won't be coming that quickly. Again, sorry.

@msft-mw
Copy link
Author

msft-mw commented Apr 12, 2021

@tillig thank you for your transparency and honesty! Because there is a workaround, this isn't a huge deal for me. I will try and find some time to dig into this, I appreciate that this is a secondary library.

@alistairjevans
Copy link
Member

alistairjevans commented Apr 13, 2021

Took a look at this briefly this morning; there may be a change that can be made to pass arguments through to Moq, so it uses provided parameters, and your test will pass.

I have two concerns here however; first, we would be conflating the Autofac TypedParameter with actual constructor parameters. The Autofac Parameter types are generally not applied 'in-order', instead they look for the best-fit for the provided parameters, based on type or position. However, Moq just requires that constructor parameters be provided in-order. We can't apply our standard constructor binding behaviour to a Moq, so we would sort of just fudge it based on the order you provided:

var argumentsToMoq = new List<object>();

foreach (var providedParam in parameters)
{
    if (providedParam is ConstantParameter constParam)
    {
        argumentsToMoq.Add(constParam.Value);
    }
}

This isn't ideal, and I can imagine a variety of confusing situations where constructors don't match unexpectedly.

Secondly, the purpose of accepting parameters in the AutoMock.Mock method is intended to provide parameters to an owning concrete type when it cannot be mocked (and is just resolved instead, with its own child constructor parameters mocked), rather than for creating mocks directly. I accept that it is a little confusing though.

I wonder if in your specific use-case @msft-mw, you should just register your mock directly into the container, like so:

var myMock = new Mock<AbstractType>(8);
myMock.Setup(q => q.DoThing()).Returns(16);

using (var mocks = AutoMock.GetLoose(cfg => cfg.RegisterMock(myMock)))
{
    AbstractType instance = mocks.Create<AbstractType>();

    Assert.Equal(16, instance.DoThing());
}

@msft-mw
Copy link
Author

msft-mw commented Apr 14, 2021

@alistairjevans thank you for investigating, and I agree that it could be a bit confusing behaviour-wise with how Moq actually works. Given the confusion that could come up if a user passed TypedParameter or ResolveParameter or something else that was out of order, expecting the types to control, and then the mock failing, perhaps it's actually the right decision that this continues to not work. Although perhaps better detection and messaging would help future users?

The workaround you suggest below was what I landed on as well, it's good to have it validated as a solution.

Lastly, if you don't mind, could you explain this a bit more?

AutoMock.Mock method is intended to provide parameters to an owning concrete type when it cannot be mocked

I always thought that when a type couldn't be mocked, that I should use AutoMock.Create<T> instead, precisely for this reason.

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

3 participants