You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在xUnit中Mock数据库?DbContext模拟失败求助

问题:FakeItEasy创建EF Core DbContext假对象失败

我的DbContext代码

public class OrganizerDbContext : IdentityDbContext<AppUser>
{
    public OrganizerDbContext(DbContextOptions<OrganizerDbContext> options)
        : base(options)
    {
    }
 
    // list of dbSet<> here

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<AppUser>()
            .HasOne(a => a.Accesses)
            .WithOne(a => a.User)
            .HasForeignKey<UserAccess>(a => a.UserId);

        builder.Entity<AppUser>()
            .HasOne(a=>a.EmploymentStatus)
            .WithOne(a=>a.User)
            .HasForeignKey<EmploymentStatus>(a => a.UserId);

        builder.Entity<EmploymentStatus>()
            .HasOne(a=>a.Facility)
            .WithMany(a=>a.Employments)
            .HasForeignKey(a => a.FacilityId);        
    }
}

测试代码(xUnit + FakeItEasy)

[Fact]
public async Task AnnouncerIndex_Test_1()
{
    // Arrange
    var fakeContext = A.Fake<OrganizerDbContext>();
    var fakeUserManager = A.Fake<UserManager<AppUser>>();
    var controller = new AnnouncementsController(fakeContext, fakeUserManager);

    // Act
    var result = await controller.IsUserBlockedFromAccesingAnnouncer();

    // Assert
    Assert.False(result);
}

第一次测试失败报错

Message: 
FakeItEasy.Core.FakeCreationException :
Failed to create fake of type Organizer3.Data.OrganizerDbContext: Below is a list of reasons for failure per attempted constructor:
Constructor with signature (Microsoft.EntityFrameworkCore.DbContextOptions1[Organizer3.Data.OrganizerDbContext]) 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 OrganizerDbContextProxy constructor must be a DbContextOptions<OrganizerDbContextProxy>. 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 Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserContext5..ctor(DbContextOptions options)
at Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext8..ctor(DbContextOptions options) at Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext3..ctor(DbContextOptions options)
at Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext1..ctor(DbContextOptions options) at Organizer3.Data.OrganizerDbContext..ctor(DbContextOptions1 options) in C:\Users\Smokiel\Desktop\Organizer3\Organizer3\Areas\Identity\Data\OrganizerDbContext.cs:line 15
at Castle.Proxies.OrganizerDbContextProxy..ctor(IInterceptor[] , DbContextOptions`1 options)

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.FakeT line 31
AnnouncementsControllerTests.AnnouncerIndex_Test_1() line 28
--- End of stack trace from previous location ---

添加无参构造函数后的报错

Message:  System.ArgumentException : Object 'Organizer3.Controllers.AnnouncementsController' of type Organizer3.Controllers.AnnouncementsController is not recognized as a fake object. Stack Trace:  DefaultFakeManagerAccessor.GetFakeManager(Object proxy) line 28 Fake.GetFakeManager(Object fakedObject) line 28 FakeConfigurationManager.GuardAgainstNonFake(Object target) line 83 FakeConfigurationManager.CallTo[T](Expression1 callSpecification) line 45 A.CallTo[T](Expression1 callSpecification) line 161 AnnouncementsControllerTests.AnnouncerIndex_Test_1() line 34 --- End of stack trace from previous location ---

解决方案

方案1:使用内存数据库创建真实DbContext(推荐,无代理问题)

如果只是需要一个不执行真实数据库操作的dummy上下文,用EF Core内存数据库创建真实实例是最简单的方案,完全满足需求且不会有FakeItEasy的代理类型冲突问题:

[Fact]
public async Task AnnouncerIndex_Test_1()
{
    // Arrange
    // 配置内存数据库选项,用唯一名称避免测试间干扰
    var dbOptions = new DbContextOptionsBuilder<OrganizerDbContext>()
        .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
        .Options;

    // 创建真实的DbContext实例(内存数据库,不会影响真实数据)
    var context = new OrganizerDbContext(dbOptions);
    var fakeUserManager = A.Fake<UserManager<AppUser>>();
    var controller = new AnnouncementsController(context, fakeUserManager);

    // Act
    var result = await controller.IsUserBlockedFromAccesingAnnouncer();

    // Assert
    Assert.False(result);
}

方案2:正确配置FakeItEasy创建DbContext假对象

如果一定要用FakeItEasy,需要手动传入匹配的DbContextOptions<OrganizerDbContext>作为构造参数,解决代理类型不匹配的问题:

[Fact]
public async Task AnnouncerIndex_Test_1()
{
    // Arrange
    var dbOptions = new DbContextOptionsBuilder<OrganizerDbContext>().Options;
    // 传入构造参数创建假对象
    var fakeContext = A.Fake<OrganizerDbContext>(dbOptions);
    var fakeUserManager = A.Fake<UserManager<AppUser>>();
    var controller = new AnnouncementsController(fakeContext, fakeUserManager);

    // Act
    var result = await controller.IsUserBlockedFromAccesingAnnouncer();

    // Assert
    Assert.False(result);
}

错误原因说明

  1. 第一次报错:FakeItEasy创建DbContext代理时会生成子类OrganizerDbContextProxy,但EF Core要求DbContextOptions<T>的泛型参数必须和上下文类型严格匹配,导致传入的DbContextOptions<OrganizerDbContext>不被代理类接受。
  2. 添加无参构造函数后的报错:大概率是测试代码中存在A.CallTo(controller)...这类针对控制器的FakeItEasy调用,但控制器不是假对象,所以报错。如果只是需要dummy上下文,方案1的内存数据库实例完全够用,无需创建假对象。

内容的提问来源于stack exchange,提问作者Samo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 22:15:36