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

InvalidCastException : Unable to cast object of type 'T' to type 'Moq.IMocked`1[T]' #9

Closed
dlurton opened this issue Aug 31, 2016 · 8 comments

Comments

@dlurton
Copy link

dlurton commented Aug 31, 2016

Using 4.0.0 of Autofac.Extras.Moq, with these classes:

public abstract class SomeAbstractClass { public virtual void SomeMethod() { throw new NotImplementedException(); } }
public class SomeClass : SomeAbstractClass { }

public class SomeBaseClassWithVirtualMethod { public virtual void SomeMethod() { throw new NotImplementedException(); } }
public class SomeClassWithVirtualMethod : SomeBaseClassWithVirtualMethod { }

The only one I can use Autofac.Extras.Mock.AutoMock.Mock<T>() to create a mock of is SomeAbstractClass. All others fail with InvalidCastException inside of Autofac.Extras.Moq.AutoMock.Mock<T>(). For example:

System.InvalidCastException : Unable to cast object of type 'MyNamespace.SomeBaseClassWithVirtualMethod' to type 'Moq.IMocked`1[MyNamespace.SomeBaseClassWithVirtualMethod]'.
   at Autofac.Extras.Moq.AutoMock.Mock[T](Parameter[] parameters)
   at MyNamespace.ScratchPad.AutoMocking_SomeBaseClassWithVirtualMethod_InvalidCastException() in <file on my local hd>

However, using regular old Moq.Mock<T> works just fine. Here's a gist with some test cases demonstrating these exceptions and that Moq.Mock works.

Either I'm missing something or there be a bug here...

@tillig
Copy link
Member

tillig commented Aug 31, 2016

I'm not going to lie, this integration library was somewhat of a "drive-by contribution" where none of the actual Autofac authors are "Moq experts" so it may take a bit for us to get to figuring out what's up here. I think either @kzu or @tkellogg wrote this.

Looking at our unit tests with abstract classes they use GetLoose() not GetStrict() - that may be part of the difference.

If that doesn't fix you up... honestly, there's not much code here - a couple of classes. It may get you an answer faster if you copy the two classes into your project and debug into it to see if there's a real bug or if there's something wrong with your expectations. I don't mean that to sound like we're punting on you, but there's a lot slipping through the cracks right now and I don't want you to get blocked up waiting on an answer that may be a long time coming.

@dlurton
Copy link
Author

dlurton commented Sep 22, 2016

I'm 99% certain there's a real bug here.

Actually, this exception will occur any time the AutoMock.Mock<T> is specifying a concrete class for T. I feel this is a bug because Moq supports mocking concrete classes that have virtual methods.

Here's why it happens:

Firstly, AutoMock constructor registers two registration sources, in this order: AnyConcreteTypeNotAlreadyRegisteredSource and MoqRegistrationHandler.

Secondly, the AutoMock.Mock<T> method just turns around and calls AutoMock.Create<T>(), casts the result to an IMocked<T>, and then returns the IMocked<T>.Mock property.

When a concrete classed is used as the type for T in AutoMock.Mock<T>, the AnyConcreteTypeNotAlreadyRegisteredSource gets it instead of the MoqRegistrationHandler. The instance returned by AutoMock.Create<T>() of course then doesn't implement IMocked<T> and causing invalid cast exception.

I think the AutoMock.Mock<T> method should guarantee that it will create a mocked object or fail with a logical exception if an attempt is made to mock something that's unmockable. It especially should not turn around and call AutoMock.Create<T>() because doing so gives up control over whether or not a mocked or real object is instantiated.

I will be working on this in my free time, which I have a little of today.

@shaynevanasperen
Copy link

This is definitely a bug, because Moq does support creating mocks of concrete classes!

@alexmg alexmg closed this as completed in aa40312 Jan 29, 2017
@alexmg
Copy link
Member

alexmg commented Jan 29, 2017

I have committed a fix for this issue and pushed a 4.1.0-rc4-242 package to NuGet.

https://www.nuget.org/packages/Autofac.Extras.Moq

I took the Gist from @dlurton and turned it into a unit test fixture which is now passing.

https://github.com/autofac/Autofac.Extras.Moq/blob/develop/test/Autofac.Extras.Moq.Test/UsageTests.cs

The .NET Framework target has been dropped to 4.5 from 4.5.1.

Moq has been updated to 4.6.38-alpha and Autofac to 4.0.1 (to allow targeting of .NET Framework 4.5).

Please take it for a spin and let me know how it goes.

@shaynevanasperen
Copy link

Thanks for doing this. But unfortunately it has broken something else now: Calling AutoMock.Create<T>() now tries to make a mock of the class instead of constructing it using the public constructor, passing in any mocks from the container for the parameters of the constructor.

Only calling AutoMock.Mock<T>() should create a mock or retrieve one that was already registered in the container. Otherwise I can't create the class under test because it wants to make a mock of it and then fails with the following exception:

Autofac.Core.DependencyResolutionException occurred
  HResult=0x80131500
  Message=An error occurred during the activation of a particular registration. See the inner exception for details. Registration: Activator = Object (DelegateActivator), Services = [DotNetStandardLibrary.Controller], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = Shared, Ownership = OwnedByLifetimeScope
  Source=Autofac
  StackTrace:
   at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
   at Autofac.Core.Lifetime.LifetimeScope.GetOrCreateAndShare(Guid id, Func`1 creator)
   at Autofac.Core.Resolving.InstanceLookup.Execute()
   at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
   at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters)
   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[TService](IComponentContext context, IEnumerable`1 parameters)
   at DotNetStandardLibrary.Tests.WithSubject`1.<.ctor>b__1_0() in C:\Code\Shayne\DotNetStandardLibrary\DotNetStandardLibrary.Tests\WithSubject.cs:line 14
   at System.Lazy`1.CreateValue()

Inner Exception 1:
InvalidProxyConstructorArgumentsException: Can not instantiate proxy of class: DotNetStandardLibrary.Controller.
Could not find a parameterless constructor.

It seems that calling AutoMock.Create<T>() is trying to run through the same code path as AutoMock.Mock<T>()

@alexmg
Copy link
Member

alexmg commented Jan 29, 2017

Thanks for the quick feedback @shaynevanasperen. Sounds like some more tests are needed. I'll try to get back on to it tomorrow.

@alexmg
Copy link
Member

alexmg commented Jan 30, 2017

I have pushed 4.1.0-rc5-246 to NuGet which hopefully fixes the problem.

Both the Create and Mock methods did attempt to resolve from the container. When the MoqRegistrationHandler registration source was updated to support classes it starting providing a registration for all classes, preventing the registration for the "System Under Test" to fall through to the AnyConcreteTypeNotAlreadyRegisteredSource. I ended up providing a list of classes that were created using the Create method to the MoqRegistrationHandler so that it now knows to ignore these.

I added a few more unit tests targeting the new class case. Fingers crossed this does the trick. 🤞

@shaynevanasperen
Copy link

Thank you @alexmg! This works now.

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