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

Strange exceptions when using AutoMock w/ AddDbContext #49

Open
cyungmann opened this issue Sep 26, 2024 · 2 comments
Open

Strange exceptions when using AutoMock w/ AddDbContext #49

cyungmann opened this issue Sep 26, 2024 · 2 comments

Comments

@cyungmann
Copy link

Describe the Bug

Get exceptions when using AutoMock w/ AddDbContext upon trying to resolve the DbContext class.

Steps to Reproduce

Download and run AutoMockTest.zip

If you switch the order of calling WorksAsync and DoesNotWorkAsync in Program.cs so that WorksAsync comes first, things suddenly and unexpectedly start working for both methods. If you only call WorksAsync things also work.

Program.cs

using Autofac;
using Autofac.Extensions.DependencyInjection;
using Autofac.Extras.Moq;
using AutoMockTest;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

await DoesNotWorkAsync();
await WorksAsync();

static async Task WorksAsync()
{
    await using var conn = new SqliteConnection(new SqliteConnectionStringBuilder
    {
        DataSource = ":memory:",
    }.ConnectionString);
    await conn.OpenAsync();

    var services = new ServiceCollection();
    services.AddLogging();
    services.AddDbContext<WidgetContext>(options =>
    {
        options.UseSqlite(conn);
    });
    var containerBuilder = new ContainerBuilder();
    containerBuilder.Populate(services);
    await using var serviceProvider = containerBuilder.Build();
    await using var db = serviceProvider.Resolve<WidgetContext>();
}

static async Task DoesNotWorkAsync()
{
    await using var conn = new SqliteConnection(new SqliteConnectionStringBuilder
    {
        DataSource = ":memory:",
    }.ConnectionString);
    await conn.OpenAsync();

    var services = new ServiceCollection();
    services.AddLogging();
    services.AddDbContext<WidgetContext>(options =>
    {
        options.UseSqlite(conn);
    });
    using var autoMock = AutoMock.GetLoose(containerBuilder =>
    {
        containerBuilder.Populate(services);
    });
    await using var db = autoMock.Create<WidgetContext>();
}

WidgetContext.cs

using Microsoft.EntityFrameworkCore;

namespace AutoMockTest;

internal sealed class WidgetContext : DbContext
{
    public DbSet<Widget> Widget { get; set; }

    public WidgetContext(DbContextOptions<WidgetContext> options) : base(options) { }
}

Widget.cs

namespace AutoMockTest;

internal sealed class Widget
{
    public long Id { get; set; }
}

AutoMockTest.csproj

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
    <PackageReference Include="Autofac.Extras.Moq" Version="6.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
  </ItemGroup>

</Project>

Expected Behavior

No exceptions should be thrown.

Exception with Stack Trace

For some reason Exception.StackTrace only has the following short stacktrace:

System.NullReferenceException: 'Object reference not set to an instance of an object.'
   at Microsoft.Extensions.Logging.Logger.<IsEnabled>g__LoggerIsEnabled|15_0(LogLevel logLevel, ILogger logger, List`1& exceptions) in /_/src/libraries/Microsoft.Extensions.Logging/src/Logger.cs:line 101

Here is a more complete callstack:

>	Microsoft.Extensions.Logging.dll!Microsoft.Extensions.Logging.Logger.IsEnabled.__LoggerIsEnabled|15_0(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.ILogger logger, ref System.Collections.Generic.List<System.Exception> exceptions) Line 101	C#
 	Microsoft.Extensions.Logging.dll!Microsoft.Extensions.Logging.Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) Line 84	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger.ShouldLog(Microsoft.EntityFrameworkCore.Diagnostics.EventDefinitionBase definition) Line 74	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Diagnostics.CoreLoggerExtensions.RedundantAddServicesCallWarning(Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger<Microsoft.EntityFrameworkCore.DbLoggerCategory.Infrastructure> diagnostics, System.IServiceProvider serviceProvider) Line 1233	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.ServiceProviderCache.GetOrAdd.__BuildServiceProvider|4_1(Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions _, (System.Collections.Concurrent.ConcurrentDictionary<Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions, (System.IServiceProvider ServiceProvider, System.Collections.Generic.IDictionary<string, string> DebugInfo)>, Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions) arguments) Line 163	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.ServiceProviderCache.GetOrAdd.AnonymousMethod__4_0(Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions contextOptions, (System.Collections.Concurrent.ConcurrentDictionary<Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions, (System.IServiceProvider ServiceProvider, System.Collections.Generic.IDictionary<string, string> DebugInfo)> _configurations, Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions options) tuples) Line 70	C#
 	System.Collections.Concurrent.dll!System.Collections.Concurrent.ConcurrentDictionary<Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions, (System.IServiceProvider, System.Collections.Generic.IDictionary<string, string>)>.GetOrAdd<(System.Collections.Concurrent.ConcurrentDictionary<Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions, (System.IServiceProvider, System.Collections.Generic.IDictionary<string, string>)>, Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions)>(Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions key, System.Func<Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions, (System.Collections.Concurrent.ConcurrentDictionary<Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions, (System.IServiceProvider, System.Collections.Generic.IDictionary<string, string>)>, Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions), (System.IServiceProvider, System.Collections.Generic.IDictionary<string, string>)> valueFactory, (System.Collections.Concurrent.ConcurrentDictionary<Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions, (System.IServiceProvider, System.Collections.Generic.IDictionary<string, string>)>, Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions) factoryArgument) Line 1222	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.ServiceProviderCache.GetOrAdd(Microsoft.EntityFrameworkCore.Infrastructure.IDbContextOptions options, bool providerRequired) Line 68	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.DbContext.DbContext(Microsoft.EntityFrameworkCore.DbContextOptions options) Line 128	C#
 	AutoMockTest.dll!AutoMockTest.WidgetContext.WidgetContext(Microsoft.EntityFrameworkCore.DbContextOptions<AutoMockTest.WidgetContext> options) Line 9	C#
 	[Lightweight Function]	
 	Autofac.dll!Autofac.Core.Activators.Reflection.BoundConstructor.Instantiate() Line 124	C#
 	Autofac.dll!Autofac.Core.Activators.Reflection.ReflectionActivator.UseSingleConstructorActivation.AnonymousMethod__0(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context, System.Action<Autofac.Core.Resolving.Pipeline.ResolveRequestContext> next) Line 200	C#
 	Autofac.dll!Autofac.Core.Resolving.Middleware.DelegateMiddleware.Execute(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context, System.Action<Autofac.Core.Resolving.Pipeline.ResolveRequestContext> next) Line 35	C#
 	Autofac.dll!Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.BuildPipeline.AnonymousMethod__1(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 276	C#
 	Autofac.dll!Autofac.Core.Resolving.Middleware.DisposalTrackingMiddleware.Execute(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context, System.Action<Autofac.Core.Resolving.Pipeline.ResolveRequestContext> next) Line 29	C#
 	Autofac.dll!Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.BuildPipeline.AnonymousMethod__1(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 276	C#
 	Autofac.Extensions.DependencyInjection.dll!Autofac.Extensions.DependencyInjection.KeyedServiceMiddleware.Execute(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context, System.Action<Autofac.Core.Resolving.Pipeline.ResolveRequestContext> next) Line 97	C#
 	Autofac.dll!Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.BuildPipeline.AnonymousMethod__1(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 276	C#
 	Autofac.dll!Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context, System.Action<Autofac.Core.Resolving.Pipeline.ResolveRequestContext> next) Line 33	C#
 	Autofac.dll!Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.BuildPipeline.AnonymousMethod__1(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 276	C#
 	Autofac.dll!Autofac.Core.Pipeline.ResolvePipeline.Invoke(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 28	C#
 	Autofac.dll!Autofac.Core.Resolving.Middleware.RegistrationPipelineInvokeMiddleware.Execute(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context, System.Action<Autofac.Core.Resolving.Pipeline.ResolveRequestContext> next) Line 28	C#
 	Autofac.dll!Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.BuildPipeline.AnonymousMethod__1(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 276	C#
 	Autofac.dll!Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute.AnonymousMethod__0() Line 42	C#
 	Autofac.dll!Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(System.Guid id, System.Func<object> creator) Line 366	C#
 	Autofac.dll!Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(System.Guid primaryId, System.Guid? qualifyingId, System.Func<object> creator) Line 386	C#
 	Autofac.dll!Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context, System.Action<Autofac.Core.Resolving.Pipeline.ResolveRequestContext> next) Line 40	C#
 	Autofac.dll!Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.BuildPipeline.AnonymousMethod__1(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 276	C#
 	Autofac.dll!Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.BuildPipeline.AnonymousMethod__1(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 276	C#
 	Autofac.dll!Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context, System.Action<Autofac.Core.Resolving.Pipeline.ResolveRequestContext> next) Line 91	C#
 	Autofac.dll!Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.BuildPipeline.AnonymousMethod__1(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 276	C#
 	Autofac.dll!Autofac.Core.Pipeline.ResolvePipeline.Invoke(Autofac.Core.Resolving.Pipeline.ResolveRequestContext context) Line 28	C#
 	Autofac.dll!Autofac.Core.Resolving.ResolveOperation.InvokePipeline(Autofac.ResolveRequest request, Autofac.Core.Resolving.Pipeline.DefaultResolveRequestContext requestContext) Line 233	C#
 	Autofac.dll!Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(Autofac.Core.ISharingLifetimeScope currentOperationScope, Autofac.ResolveRequest request) Line 132	C#
 	Autofac.dll!Autofac.Core.Resolving.ResolveOperation.ExecuteOperation(Autofac.ResolveRequest request) Line 180	C#
 	Autofac.dll!Autofac.Core.Resolving.ResolveOperation.Execute(Autofac.ResolveRequest request) Line 47	C#
 	Autofac.dll!Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(Autofac.ResolveRequest request) Line 333	C#
 	Autofac.dll!Autofac.Core.Container.ResolveComponent(Autofac.ResolveRequest request) Line 145	C#
 	Autofac.dll!Autofac.Core.Container.Autofac.IComponentContext.ResolveComponent(Autofac.ResolveRequest request)	Unknown
 	Autofac.dll!Autofac.ResolutionExtensions.TryResolveService(Autofac.IComponentContext context, Autofac.Core.Service service, System.Collections.Generic.IEnumerable<Autofac.Core.Parameter> parameters, out object instance) Line 1107	C#
 	Autofac.dll!Autofac.ResolutionExtensions.ResolveService(Autofac.IComponentContext context, Autofac.Core.Service service, System.Collections.Generic.IEnumerable<Autofac.Core.Parameter> parameters) Line 870	C#
 	Autofac.dll!Autofac.ResolutionExtensions.Resolve(Autofac.IComponentContext context, System.Type serviceType, System.Collections.Generic.IEnumerable<Autofac.Core.Parameter> parameters) Line 336	C#
 	Autofac.dll!Autofac.ResolutionExtensions.Resolve(Autofac.IComponentContext context, System.Type serviceType, Autofac.Core.Parameter[] parameters) Line 352	C#
 	Autofac.Extras.Moq.dll!Autofac.Extras.Moq.AutoMock.Create(bool isMock, System.Type serviceType, Autofac.Core.Parameter[] parameters) Line 191	C#
 	Autofac.Extras.Moq.dll!Autofac.Extras.Moq.AutoMock.Create<AutoMockTest.WidgetContext>(bool isMock, Autofac.Core.Parameter[] parameters) Line 196	C#
 	Autofac.Extras.Moq.dll!Autofac.Extras.Moq.AutoMock.Create<AutoMockTest.WidgetContext>(Autofac.Core.Parameter[] parameters) Line 144	C#
 	AutoMockTest.dll!Program.<Main>$.__DoesNotWorkAsync|0_1() Line 50	C#
 	AutoMockTest.dll!Program.<Main>$(string[] args) Line 9	C#
 	AutoMockTest.dll!Program.<Main>(string[] args)	Unknown

Dependency Versions

Autofac: 8.1.0
Autofac.Extras.Moq: 6.1.1
Autofac.Extensions.DependencyInjection: 10.0.0
Microsoft.EntityFrameworkCore.Sqlite: 8.0.8
Microsoft.Extensions.Logging: 8.0.0

Additional Info

The fact that things work when not using AutoMock suggests to me that the problem is in AutoMock somehow. It's also completely possible that I'm doing something wrong.

@olstakh
Copy link

olstakh commented Dec 23, 2024

@cyungmann
In the second case (DoesNotWork one) - you seem to not build the resulting container (i.e. containerBuilder.Build()), so .AddLogging() might not have effect.

I suspect there's some static internal logging state, that gets properly initialized in the working case (because container is actually built, including logger), so by the time it gets to the not working scenario - it now works. But running in reverse will throw because container is not built.

EDIT: this assumption might be wrong, i overlooked the fact that automock is instantiated from a container, which is probably being built. But again, the fact that order matters tells me there's some static factory isn't getting initialized in a second case

@olstakh
Copy link

olstakh commented Dec 23, 2024

The difference i see is in Automock's case - LoggerFactory is created with CastleProxy provider that eventually leads to null logger instance. In regular working case - LoggerFactory is created without any providers (collection is empty).
And yes, caching seems to happen on EntityFramework level, which explains why order matters

EDIT:
The issue might be in MoqRegistrationHandler. If you add the following to your working case:

    containerBuilder.RegisterInstance(new MockRepository(MockBehavior.Loose));

    // [reflection] equivalent to builder.RegisterSource(new MoqRegistrationHandler(new HashSet<Type>(), new HashSet<Type>()));
    containerBuilder.RegisterSource(typeof(Autofac.Extras.Moq.AutoMock).Assembly
        .GetType("Autofac.Extras.Moq.MoqRegistrationHandler")
        .GetConstructors()
        .Single()
        .Invoke(new object?[] {new HashSet<Type>(), new HashSet<Type>()})
        as Autofac.Core.IRegistrationSource); 

you'll get same issue. It's possible this registration source has a bug for your case
P. S. Same if you pass [typeof(WidgetContext)] to any of the sets for MoqRegistrationHandler constructor

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

2 participants