【问题标题】:How to do UnitOfWork + Repository patterns with entity framework core and unit testing如何使用实体框架核心和单元测试来做 UnitOfWork + Repository 模式
【发布时间】:2021-04-02 12:29:48
【问题描述】:

我在单元测试时遇到了一个问题,如果我一次运行多个测试,DbContext 将丢失我在单元测试期间添加的记录,我认为这可能与服务的方式有关在我的ServiceCollection注册。

我有以下设置:

IUnitOfWork:

    public interface IUnitOfWork : IDisposable
    {
        IUserRepository Users { get; }

        int Complete();
    }

工作单位

    public class UnitOfWork : IUnitOfWork
    {
        private readonly MyDbContext _context;

        public IUserRepository Users { get; }

        public UnitOfWork(MyDbContext context,
                          IUserRepository userRepository)
        {
            _context = context;

            Users = userRepository;
        }

        public void Dispose() => _context.Dispose();

        public int Complete() => _context.SaveChanges();
    }

用户存储库

    public class UserRepository : Repository<User>, IUserRepository
    {
        public UserRepository(MyDbContext context) : base(context) { }

        public MyDbContext MyDbContext => Context as MyDbContext;

        public Task<User?> GetUserDetailsAsync(int userID)
        {
            var user = MyDbContext.Users.Where(user => user.Id == userID)
                                                  .Include(user => user.Emails)
                                                  .Include(user => user.PhoneNumbers).FirstOrDefault();

            return Task.FromResult(user);
        }
    }

这是我的基本测试:

    public abstract class BaseTest : IDisposable
    {
        protected ServiceProvider ServiceProvider { get; }

        private MyDbContext MyDbContext { get; }

        protected IUnitOfWork UnitOfWork { get; }

        public BaseTest()
        {
            var serviceCollection = new ServiceCollection();
            serviceCollection.AddScoped<IUserService, UserService>()
                             .AddScoped<IUnitOfWork, UnitOfWork>()
                             .AddScoped(typeof(IRepository<>), typeof(Repository<>))
                             .AddScoped<IOrganizationRepository, OrganizationRepository>()
                             .AddScoped<IExercisePostRepository, ExercisePostRepository>()
                             .AddScoped<IUserRepository, UserRepository>()
                             .AddTransient<IRestClient, RestClient>()
                             .AddAutoMapper(typeof(Startup).Assembly)
                             .AddDbContext<MyDbContext>(options =>
                                                                  options.UseInMemoryDatabase("Core")
                                                                         .EnableSensitiveDataLogging());
            ServiceProvider = serviceCollection.BuildServiceProvider();
            SpotcheckrCoreContext = ServiceProvider.GetRequiredService<MyDbContext>();
            MyDbContext.Database.EnsureCreated();
            UnitOfWork = ServiceProvider.GetRequiredService<IUnitOfWork>();
        }

        public void Dispose()
        {
            MyDbContext.Database.EnsureDeleted();
            UnitOfWork.Dispose();
        }
    }

样本测试:

    public class UserServiceTests : BaseTest
    {
        private readonly IUnitOfWork UnitOfWork;

        private readonly IUserService Service;

        public UserServiceTests()
        {
            UnitOfWork = ServiceProvider.GetRequiredService<IUnitOfWork>();
            Service = ServiceProvider.GetRequiredService<IUserService>();
        }

        [Fact]
        public async void GetUserAsync_WithValidUser_ReturnsUser()
        {
            var user = new User
            {
                FirstName = "John",
                LastName = "Doe"
            };
            UnitOfWork.Users.Add(user);
            UnitOfWork.Complete();

            var result = await Service.GetUserAsync(user.Id);
            Assert.Equal(user.Id, result.Id);
        }
}

如果我自己运行这个测试,那么它将正确通过,我可以在存储库中看到用户。但是,如果我使用其他测试运行它并进行调试,那么一旦我在存储库中检查 UnitOfWork.Users,该用户就会丢失,但我确实在测试中的 UnitOfWork.Users 中看到它。

这里的正确方法是什么?

编辑 1: 尝试了其他一些更改,但还没有运气。调整UnitOfWork 以接收每个存储库的接口并将它们注册到BaseTest 作为范围服务。还尝试将BaseTest 标记为实现IDisposable,然后执行:

        public void Dispose()
        {
            MyDbContext.Database.EnsureDeleted();
            UnitOfWork.Dispose();
        }

在服务层我会看到用户很好,但是一旦我进入存储库层,我就会失去用户:/我怀疑它与依赖注入 AddScopedAddTransient 有关以及所有这些如何与运行多个单元测试一起工作。

编辑 2: 尝试了更多的东西...在每个测试类上使用了IClassFixture&lt;BaseTest&gt;,然后确保每个测试类都实现了IDisposable,在那里我将确保删除上下文数据库;还确保在测试类构造函数中创建了它。有了这个,我最终得到了以下错误:

The instance of entity type cannot be tracked because another instance with the same key value for {'Id'} is already being tracked

所以我添加了.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking),但问题仍然存在。

这设置起来很烦人。

【问题讨论】:

    标签: .net-core entity-framework-core xunit


    【解决方案1】:

    这就是现在为我解决的问题。

    总结:创建了一个新的ServiceFixture。此ServiceFixtureIClassFixture&lt;ServiceFixture&gt; 的形式应用于BaseTest 类。 ServiceFixture 负责初始化服务集合并允许在不同的测试类中重用它。 BaseTest 的目的是允许在每次测试后处理数据库和其他必要的清理工作。该类的Dispose 方法将分离实体状态并删除数据库。

    ServiceFixture.cs

    public class ServiceFixture
        {
            public ServiceProvider ServiceProvider { get; }
    
            public ServiceFixture()
            {
                var serviceCollection = new ServiceCollection();
                serviceCollection.AddScoped<IUserService, UserService>()
                                 .AddScoped<ICertificationService, CertificationService>()
                                 .AddScoped<IOrganizationService, OrganizationService>()
                                 .AddScoped<ICertificateService, CertificateService>()
                                 .AddScoped<IUnitOfWork, UnitOfWork>()
                                 .AddScoped<IUserRepository, UserRepository>()
                                 .AddScoped<IExercisePostRepository, ExercisePostRepository>()
                                 .AddScoped<IEmailRepository, EmailRepository>()
                                 .AddScoped<IPhoneNumberRepository, PhoneNumberRepository>()
                                 .AddScoped<ICertificationRepository, CertificationRepository>()
                                 .AddScoped<ICertificateRepository, CertificateRepository>()
                                 .AddScoped<IOrganizationRepository, OrganizationRepository>()
                                 .AddTransient<IRestClient, RestClient>()
                                 .AddSingleton<NASMCertificationValidator>()
                                 .AddAutoMapper(typeof(Startup).Assembly)
                                 .AddDbContext<SpotcheckrCoreContext>(options =>
                                                                      options.UseInMemoryDatabase("Spotcheckr-Core")
                                                                             .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
                                                                             .EnableSensitiveDataLogging());
                ServiceProvider = serviceCollection.BuildServiceProvider();
            }
        }
    

    BaseTest.cs

        public abstract class BaseTest : IClassFixture<ServiceFixture>, IDisposable
        {
            protected readonly ServiceProvider ServiceProvider;
    
            protected readonly IUnitOfWork UnitOfWork;
    
            private readonly SpotcheckrCoreContext Context;
    
            public BaseTest(ServiceFixture serviceFixture)
            {
                ServiceProvider = serviceFixture.ServiceProvider;
                Context = serviceFixture.ServiceProvider.GetRequiredService<SpotcheckrCoreContext>();
                UnitOfWork = serviceFixture.ServiceProvider.GetRequiredService<IUnitOfWork>();
    
                Context.Database.EnsureCreated();
            }
    
            public void Dispose()
            {
                Context.ChangeTracker.Entries().ToList().ForEach(entry => entry.State = EntityState.Detached);
                Context.Database.EnsureDeleted();
            }
        }
    

    UserServiceTests.cs

        public class UserServiceTests : BaseTest
        {
            private readonly IUserService Service;
    
            public UserServiceTests(ServiceFixture serviceFixture) : base(serviceFixture)
            {
                Service = serviceFixture.ServiceProvider.GetRequiredService<IUserService>();
            }
    
            [Fact]
            public async void GetUserAsync_WithInvalidUser_ThrowsException()
            {
                Assert.ThrowsAsync<InvalidOperationException>(() => Service.GetUserAsync(-1));
            }
    
            [Fact]
            public void CreateUser_UserTypeAthlete_CreatesAthleteUser()
            {
                var result = Service.CreateUser(Models.UserType.Athlete);
                Assert.IsType<Athlete>(result);
            }
    
            [Fact]
            public void CreateUser_UserTypePersonalTrainer_CreatesPersonalTrainerUser()
            {
                var result = Service.CreateUser(Models.UserType.PersonalTrainer);
                Assert.IsType<PersonalTrainer>(result);
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多