.net core 如何做UnitOfWork+;具有实体框架核心和单元测试的存储库模式

.net core 如何做UnitOfWork+;具有实体框架核心和单元测试的存储库模式,.net-core,entity-framework-core,xunit,.net Core,Entity Framework Core,Xunit,我在单元测试时遇到了一个问题,如果我同时运行多个测试,DbContext将丢失我在单元测试期间添加的记录,我认为这可能与如何在我的servicecolection中注册服务有关 我有以下设置: i工作内容: public interface IUnitOfWork : IDisposable { IUserRepository Users { get; } int Complete(); } 工作单元 public class

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

我有以下设置:

i工作内容:

    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);
        }
    }
在服务层,我会看到用户很好,但一旦我进入存储库层,我就会失去用户:/我怀疑这与依赖注入
AddScoped
vs
AddTransient
有关,以及所有这些与运行多个单元测试的工作方式有关

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

无法跟踪实体类型的实例,因为已在跟踪另一个具有{'Id'}相同键值的实例

因此我添加了
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
,但问题仍然存在


这对安装程序来说非常烦人。

这就是我现在解决这个问题的方法

摘要:创建了一个新的
ServiceFixture
。此
ServiceFixture
作为
IClassFixture
应用于
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();
        }
    }
公共类ServiceFixture
{
公共服务提供程序服务提供程序{get;}
公共服务设施()
{
var servicecolection=新servicecolection();
serviceCollection.AddScoped()
.addScope()文件
.addScope()文件
.addScope()文件
.addScope()文件
.addScope()文件
.addScope()文件
.addScope()文件
.addScope()文件
.addScope()文件
.addScope()文件
.addScope()文件
.AddTransient()
.AddSingleton()
.AddAutoMapper(类型(启动).Assembly)
.AddDbContext(选项=>
选项。使用MemoryDatabase(“Spotcheckr Core”)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NotTracking)
.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();
        }
    }
公共抽象类BaseTest:IClassFixture,IDisposable
{
受保护的只读服务提供程序服务提供程序;
受保护的只读IUnitOfWork;
私有只读SpotcheckrCoreContext上下文;
公共基础测试(ServiceFixture ServiceFixture)
{
ServiceProvider=serviceFixture.ServiceProvider;
Context=serviceFixture.ServiceProvider.GetRequiredService();
UnitOfWork=serviceFixture.ServiceProvider.GetRequiredService();
Context.Database.recreated();
}
公共空间处置()
{
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);
        }
    }
公共类UserServiceTests:BaseTest { 专用只读IUserService服务; 公共UserServiceTests(ServiceFixture ServiceFixture):基础(ServiceFixture) { Service=serviceFixture.ServiceProvider.GetRequiredService(); } [事实] public async void GetUserAsync\u with invalidUser\u ThrowsException() { Assert.ThrowsAsync(()=>Service.GetUserAsync(-1)); } [事实] public void CreateUser\u用户类型运动员\u CreateSathletueser() { var result=Service.CreateUser(Models.UserType.athleter); Assert.IsType(结果); } [事实] public void CreateUser_用户类型PersonalTrainer_CreatesPersonalTrainerUser() { var result=Service.CreateUser(Models.UserType.PersonalTrainer); Assert.IsType(结果); } }
这就是我目前所能解决的问题

摘要:创建了一个新的
ServiceFixture
。此
ServiceFixture
作为
IClassFixture
应用于
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();
        }
    }
公共类ServiceFixture
{
公共服务提供程序服务提供程序{get;}
公共服务设施()
{
var servicecolection=新servicecolection();
    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();
        }
    }
    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);
        }
    }