Skip to main content

abp-testing

ABP testing patterns - integration tests over unit tests, GetRequiredService, IDataSeedContributor, Shouldly assertions, AddAlwaysAllowAuthorization, NSubstitute mocking, WithUnitOfWorkAsync. Use when writing or reviewing tests for application services, domain services, or repositories in ABP projects.

来源信息

仓库
abpframework/abp
最近来源活动
2026年3月22日 17:13
检测到的 SKILL.md 语言
英语
星标
14,437
分支
3,722

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
abp-testing
description
ABP testing patterns - integration tests over unit tests, GetRequiredService, IDataSeedContributor, Shouldly assertions, AddAlwaysAllowAuthorization, NSubstitute mocking, WithUnitOfWorkAsync. Use when writing or reviewing tests for application services, domain services, or repositories in ABP projects.
# ABP Testing Patterns > **Docs**: https://abp.io/docs/latest/testing ## Test Project Structure | Project | Purpose | Base Class | |---------|---------|------------| | `*.Domain.Tests` | Domain logic, entities, domain services | `*DomainTestBase` | | `*.Application.Tests` | Application services | `*ApplicationTestBase` | | `*.EntityFrameworkCore.Tests` | Repository implementations | `*EntityFrameworkCoreTestBase` | ## Integration Test Approach ABP recommends integration tests over unit tests: - Tests run with real services and database (SQLite in-memory) - No mocking of internal services - Each test gets a fresh database instance ## Application Service Test ```csharp public class BookAppService_Tests : MyProjectApplicationTestBase { private readonly IBookAppService _bookAppService; public BookAppService_Tests() { _bookAppService = GetRequiredService<IBookAppService>(); } [Fact] public async Task Should_Get_List_Of_Books() { // Act var result = await _bookAppService.GetListAsync( new PagedAndSortedResultRequestDto() ); // Assert result.TotalCount.ShouldBeGreaterThan(0); result.Items.ShouldContain(b => b.Name == "Test Book"); } [Fact] public async Task Should_Create_Book() { // Arrange var input = new CreateBookDto { Name = "New Book", Price = 19.99m }; // Act var result = await _bookAppService.CreateAsync(input); // Assert result.Id.ShouldNotBe(Guid.Empty); result.Name.ShouldBe("New Book"); result.Price.ShouldBe(19.99m); } [Fact] public async Task Should_Not_Create_Book_With_Invalid_Name() { // Arrange var input = new CreateBookDto { Name = "", // Invalid Price = 10m }; // Act & Assert await Should.ThrowAsync<AbpValidationException>(async () => { await _bookAppService.CreateAsync(input); }); } } ``` ## Domain Service Test ```csharp public class BookManager_Tests : MyProjectDomainTestBase { private readonly BookManager _bookManager; private readonly IBookRepository _bookRepository; public BookManager_Tests() { _bookManager = GetRequiredService<BookManager>(); _bookRepository = GetRequiredService<IBookRepository>(); } [Fact] public async Task Should_Create_Book() { // Act var book = await _bookManager.CreateAsync("Test Book", 29.99m); // Assert book.ShouldNotBeNull(); book.Name.ShouldBe("Test Book"); book.Price.ShouldBe(29.99m); } [Fact] public async Task Should_Not_Allow_Duplicate_Book_Name() { // Arrange await _bookManager.CreateAsync("Existing Book", 10m); // Act & Assert var exception = await Should.ThrowAsync<BusinessException>(async () => { await _bookManager.CreateAsync("Existing Book", 20m); }); exception.Code.ShouldBe("MyProject:BookNameAlreadyExists"); } } ``` ## Test Naming Convention Use descriptive names: ```csharp // Pattern: Should_ExpectedBehavior_When_Condition public async Task Should_Create_Book_When_Input_Is_Valid() public async Task Should_Throw_BusinessException_When_Name_Already_Exists() public async Task Should_Return_Empty_List_When_No_Books_Exist() ``` ## Arrange-Act-Assert (AAA) ```csharp [Fact] public async Task Should_Update_Book_Price() { // Arrange var bookId = await CreateTestBookAsync(); var newPrice = 39.99m; // Act var result = await _bookAppService.UpdateAsync(bookId, new UpdateBookDto { Price = newPrice }); // Assert result.Price.ShouldBe(newPrice); } ``` ## Assertions with Shouldly ABP uses Shouldly library: ```csharp result.ShouldNotBeNull(); result.Name.ShouldBe("Expected Name"); result.Price.ShouldBeGreaterThan(0); result.Items.ShouldContain(x => x.Id == expectedId); result.Items.ShouldBeEmpty(); result.Items.Count.ShouldBe(5); // Exception assertions await Should.ThrowAsync<BusinessException>(async () => { await _service.DoSomethingAsync(); }); var ex = await Should.ThrowAsync<BusinessException>(async () => { await _service.DoSomethingAsync(); }); ex.Code.ShouldBe("MyProject:ErrorCode"); ``` ## Test Data Seeding ```csharp public class MyProjectTestDataSeedContributor : IDataSeedContributor, ITransientDependency { public static readonly Guid TestBookId = Guid.Parse("..."); private readonly IBookRepository _bookRepository; private readonly IGuidGenerator _guidGenerator; public async Task SeedAsync(DataSeedContext context) { await _bookRepository.InsertAsync( new Book(TestBookId, "Test Book", 19.99m, Guid.Empty), autoSave: true ); } } ``` ## Disabling Authorization in Tests ```csharp public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAlwaysAllowAuthorization(); } ``` ## Mocking External Services Use NSubstitute when needed: ```csharp public override void ConfigureServices(ServiceConfigurationContext context) { var emailSender = Substitute.For<IEmailSender>(); emailSender.SendAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>()) .Returns(Task.CompletedTask); context.Services.AddSingleton(emailSender); } ``` ## Testing with Specific User ```csharp [Fact] public async Task Should_Get_Current_User_Books() { // Login as specific user await WithUnitOfWorkAsync(async () => { using (CurrentUser.Change(TestData.UserId)) { var result = await _bookAppService.GetMyBooksAsync(); result.Items.ShouldAllBe(b => b.CreatorId == TestData.UserId); } }); } ``` ## Testing Multi-Tenancy ```csharp [Fact] public async Task Should_Filter_Books_By_Tenant() { using (CurrentTenant.Change(TestData.TenantId)) { var result = await _bookAppService.GetListAsync(new GetBookListDto()); // Results should be filtered by tenant } } ``` ## Best Practices - Each test should be independent - Don't share state between tests - Use meaningful test data - Test edge cases and error conditions - Keep tests focused on single behavior - Use test data seeders for common data - Avoid testing framework internals
在 GitHub 查看