使用FakeEasy在.NET中Mock控制器构造参数时遇错求助
我是.NET新手,给TagController写单元测试时,用FakeEasy模拟依赖的ApplicationDbContext,抛出了FakeCreationException。后来发现指定构造参数的写法能正常运行,想知道原因。
测试类代码
public class TagControllerTest { private TagController _tagController; private ApplicationDbContext _applicationDbContextMock; public TagControllerTest() { _applicationDbContextMock = A.Fake<ApplicationDbContext>(); _tagController = new TagController(_applicationDbContextMock); } [Fact] public void TagController_Add_ReturnSuccess() { var response = _tagController.Add(); response.Should().BeOfType<IActionResult>(); } }
错误信息
FakeItEasy.Core.FakeCreationException : Failed to create fake of type blog_dotnet_mvc.DataAccess.ApplicationDbContext: Below is a list of reasons for failure per attempted constructor: Constructor with signature (Microsoft.EntityFrameworkCore.DbContextOptions`1[blog_dotnet_mvc.DataAccess.ApplicationDbContext]) failed: No constructor matches the passed arguments for constructor. An exception of type System.InvalidOperationException was caught during this call. Its message was: The DbContextOptions passed to the ApplicationDbContextProxy constructor must be a DbContextOptions<ApplicationDbContextProxy>. When registering multiple DbContext types, make sure that the constructor for each context type has a DbContextOptions<TContext> parameter rather than a non-generic DbContextOptions parameter. at Microsoft.EntityFrameworkCore.DbContext..ctor(DbContextOptions options) at blog_dotnet_mvc.DataAccess.ApplicationDbContext..ctor(DbContextOptions`1 options) in E:\localrepo\blog-dotnet-mvc\blog-dotnet-mvc\DataAccess\ApplicationDbContext.cs:line 8 at Castle.Proxies.ApplicationDbContextProxy..ctor(IInterceptor[], DbContextOptions`1 options) at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) at System.Reflection.ConstructorInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) Stack Trace: FailedCreationResult.get_Result() line 82 FakeAndDummyManager.CreateFake(Type typeOfFake, Action`1 optionsBuilder, LoopDetectingResolutionContext resolutionContext) line 42 FakeAndDummyManager.CreateFake(Type typeOfFake, LoopDetectingResolutionContext resolutionContext) line 28 A.Fake[T]() line 31 TagControllerTest.ctor() line 24 RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)
TagController代码
namespace blog_dotnet_mvc.Controllers { public class TagController : Controller { private readonly ApplicationDbContext _dbContext; public TagController(ApplicationDbContext dbContext) { _dbContext = dbContext; } [HttpGet] public IActionResult Add() { return View(); } } }
ApplicationDbContext代码
using blog_dotnet_mvc.Models.Domain; using Microsoft.EntityFrameworkCore; namespace blog_dotnet_mvc.DataAccess { public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<BlogPost> BlogPosts { get; set; } public DbSet<Tag> Tags { get; set; } } }
有效写法
_applicationDbContextMock = A.Fake<ApplicationDbContext>(x => x.WithArgumentsForConstructor(() => new ApplicationDbContext(new DbContextOptions<ApplicationDbContext>())));
问题原因解释
核心报错逻辑
FakeEasy创建ApplicationDbContext的代理对象时,会自动生成一个ApplicationDbContextProxy子类(继承自你的ApplicationDbContext)。当它调用父类构造函数时,传入的是DbContextOptions<ApplicationDbContext>,但EF Core的DbContext基类会严格校验:传入的DbContextOptions泛型参数必须和当前实例的类型完全匹配——也就是需要DbContextOptions<ApplicationDbContextProxy>,而非你定义的DbContextOptions<ApplicationDbContext>,这直接触发了InvalidOperationException。
指定构造参数的解决原理
用WithArgumentsForConstructor显式指定构造参数时,FakeEasy会直接使用你提供的DbContextOptions<ApplicationDbContext>调用原始ApplicationDbContext的构造函数,跳过了自动生成代理类适配参数的逻辑。此时EF Core的校验会通过,因为构造函数接收的参数类型和当前实例的父类类型完全匹配。
更优实践
不要直接模拟DbContext,建议抽象出数据访问层的接口(比如ITagRepository),然后模拟这个接口。这样既可以避免EF Core的构造参数限制,还能让单元测试更聚焦于控制器的业务逻辑,而非EF的内部细节。
内容的提问来源于stack exchange,提问作者Kavishka Madhushan

