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

4.1.0 Update using System.MissingException. The lazily-initialized type does not have a public, parameterless constructor. #15

Closed
phillipzada opened this issue Oct 2, 2017 · 4 comments
Labels

Comments

@phillipzada
Copy link

With the latest update (4.1.0), tests that worked previously with lazy loaded dependencies are failing with the following error when Lazy types are initialised i.e. *.Value is called.

Controller being tested

    public class ListingStatesController : ApiController
    {
        private readonly Lazy<IListingStateRepository> _listingStateRepository;

        public ListingStatesController(
            Lazy<IUnitOfWork> unitOfWork,
            Lazy<IMapper> mapper,
            Lazy<IListingStateRepository> listingStateRepository) : base(unitOfWork, mapper)
        {
            _listingStateRepository = listingStateRepository;
        }

        [HttpGet]
        public async Task<IActionResult> GetAllAsync()
        {
            return Ok(await _listingStateRepository.Value.GetAllAsync()); // FAILS HERE
        }
    }

Unit Test Example

[Fact]
        public async void GetAllAsyncShouldReturnOkWithContent()
        {
            using (var mock = AutoMock.GetLoose())
            {
                // Arrange

                // inject dependency from mock repository
                mock.Provide(_repositories.Resolve<IListingStateRepository>());
                var sut = mock.Create<ListingStatesController>();
                var index = 1;
                var models = new[]
                {
                    new ListingState() {Id = index++, Name = "State 1"},
                    new ListingState() {Id = index++, Name = "State 2"},
                    new ListingState() {Id = index, Name = "State 3"}
                };
                var context = _repositories.Resolve<MockDbContext>();
                context.Set<ListingState>().AddRange(models);
                context.SaveChanges();

                // Act
                var result = await sut.GetAllAsync();

                // Assert
                mock.Mock<IListingStateRepository>().Verify(x => x.GetAllAsync(), Times.Once);
                var actionResult = Assert.IsType<OkObjectResult>(result);
                var model = Assert.IsAssignableFrom<QueryableResult<ListingStateResponseDto>>(actionResult.Value);
                Assert.Equal(models.Length, model.RecordCount);
                Assert.Equal(models.Length, model.Count);

            }

        }

When upgrading the package this bracks, however when reverting back to 4.0.0 works. Moq version is 4.7.127 for reference.

@tillig
Copy link
Member

tillig commented Oct 2, 2017

Have you tried Autofac.Extras.Moq 4.2.0?

@tillig
Copy link
Member

tillig commented Oct 2, 2017

If it still fails in 4.2.0, can you update your unit test to be something we could run?

  • Simplify it where possible - if you don't need all of the models, etc., remove them. That will help to focus on where problems are showing up.
  • Make it standalone - there's use of a _repositories.Resolve<IListingStateRepository>() and it's unclear where that comes from.

Also, include the exception information here so we can see what's actually breaking. If we both can't run the test and we don't know what the exception is, we're stuck.

@Andorbal
Copy link

I think I'm running into a similar issue. With v4.0.0, I could use mock.Create() on a class that had a Func that returned an object that only had a parameterless constructor. Since AutoMock couldn't resolve concrete types, it would not try to mock and pass resolution on to Autofac. Autofac could figure out what to do because AutoMock has Autofac auto-register all concrete types.

Since v4.1 added the ability to resolve concrete classes, AutoMock no longer passes these on to Autofac and instead tries to mock the object. This fails since Moq (Well, Castle.Proxy) needs a parameterless constructor to create the mock. This same behavior exists in v4.2.

This is the simplest test I could put together that shows this failure.

[Fact]
public void ResolvingFuncWithConstructorArgumentsShouldNotThrow()
{
    using (var mock = AutoMock.GetStrict())
    {
        var testObject = mock.Create<ConcreteWithFuncDependency>();
        var result = testObject.Run();
        Assert.NotNull(result);
    }
}

public interface IFoo
{
}

public class ConcreteWithConstructorArguments
{
    private readonly IFoo foo;

    public ConcreteWithConstructorArguments(IFoo foo)
    {
        this.foo = foo;
    }
}

public class ConcreteWithFuncDependency
{
    private readonly Func<ConcreteWithConstructorArguments> getDependency;

    public ConcreteWithFuncDependency(Func<ConcreteWithConstructorArguments> getDependency)
    {
        this.getDependency = getDependency;
    }

    public ConcreteWithConstructorArguments Run() => getDependency();
}

I can fix the issue by replacing MoqRegistrationHandler.ServiceIsAbstractOrNonSealedOrInterface with the following version:

private static bool ServiceIsAbstractOrNonSealedOrInterface(IServiceWithType typedService)
{
    var serverTypeInfo = typedService.ServiceType.GetTypeInfo();

    return serverTypeInfo.IsInterface
        || serverTypeInfo.IsAbstract
        || (serverTypeInfo.IsClass &&
            !serverTypeInfo.IsSealed &&
            typedService.ServiceType.GetConstructors().Any(x => !x.GetParameters().Any()));
}

With this fix, all tests pass including the test above. I realize that the fix would need to be made a little more robust because it fails for classes that have protected parameterless constructors, and I'm sure a few other scenarios. If this seems like something that should be fixed, I can add tests for edge cases and include the proper binding flags to GetConstructor.

Let me know if you want a pull request.

Thanks!

@ocdi
Copy link

ocdi commented Dec 9, 2017

I've hit a similar issue since upgrading to 4.2.0 of this, I have some older code that has concrete dependencies that depend on other concrete dependencies, that is no longer working. This is an example test case that used to work but no longer works on 4.2 due to this issue.

using Autofac.Extras.Moq;
using Xunit;

namespace XUnitTestProject1
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            using (var env = AutoMock.GetLoose())
            {

                var sut = env.Create<Sut>();
                var result = sut.Test();
                Assert.True(result);

            }
        }
    }

    public class Sut
    {
        private readonly Concrete1 _concrete1;

        public Sut(Concrete1 concrete1)
        {
            _concrete1 = concrete1;
        }

        internal bool Test() => _concrete1.Test1();
    }

    public class Concrete1
    {
        private readonly Concrete2 _concrete2;

        public Concrete1(Concrete2 concrete2)
        {
            _concrete2 = concrete2;
        }

        internal bool Test1() => _concrete2.Test2();
    }

    public class Concrete2
    {
        internal bool Test2() => true;
    }
}

@tillig tillig added the bug label Aug 16, 2018
@tillig tillig closed this as completed in c8bf5c1 Oct 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants