.net-core - 如何使用实体框架核心和单元测试来做 UnitOfWork + Repository 模式
问题描述
我在单元测试时遇到了一个问题,如果我一次运行多个测试,DbContext
将会丢失我在单元测试期间添加的记录,我认为这可能与服务在我的ServiceCollection
.
我有以下设置:
工作单位:
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();
}
在服务层中,我会看到用户很好,但是一旦我进入存储库层,我就会失去用户:/我怀疑它与依赖注入有关,AddScoped
以及AddTransient
所有这些如何与运行多个单元测试。
编辑2:尝试了更多的东西......IClassFixture<BaseTest>
在每个测试类上使用,然后确保每个测试类都实现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)
,但问题仍然存在。
这设置起来很烦人。
解决方案
这就是现在为我解决的问题。
摘要:创建了一个新的ServiceFixture
. 这ServiceFixture
适用于BaseTest
类IClassFixture<ServiceFixture>
。ServiceFixture
负责初始化服务集合并允许在不同的测试类中重用它。的目的BaseTest
是允许在每次测试后处理数据库和其他必要的清理。Dispose
该类的方法将分离实体状态并删除数据库。
服务夹具.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();
}
}
用户服务测试.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);
}
}
推荐阅读
- javascript - Angular 反应式表单提交事件
- bash - 获取 bash 函数的除前 N 个参数之外的所有参数
- video-streaming - ThingsBoard 仪表板的视频源
- javascript - 具有多个条件的 for 循环不起作用(JS)
- vb.net - 为什么关闭打开应用程序后设置不保存,使用vb.net
- java - 使用 PATCH 方法删除列表的特定元素,该元素是另一个列表的元素。使用 dropwizard 编写
- python - 如何在 Python 中创建打印速度较慢的代码
- firebase - firestore 更改未触发
- android-fragments - Xamarin.Android 应用程序使用片段,如何在片段内的小部件上绑定处理程序
- google-sheets - 如何在日期和小于之间使用 Google 表格中的 COUNTIFS