| name | abp-testing |
| description | ABP Framework v10.x (10.4/10.5) testing: integration tests, *TestBase classes (Domain/Application/EntityFrameworkCore), SQLite in-memory, Shouldly, NSubstitute, data seeding, CurrentUser/CurrentTenant.Change, AddAlwaysAllowAuthorization. Use when you need to write unit/integration tests in ABP. |
ABP Framework — Testing
ABP Framework v10.x (10.4/10.5) testing guide. ABP prefers integration tests over unit tests: they run with real services + a real (SQLite in-memory) database, and internal services are not mocked.
Trigger
- "write ABP test"
- "ABP integration test"
- "ABP unit test"
- "ABP TestBase"
- "ABP test data seed"
- "ABP authorization test"
- "ABP Shouldly / NSubstitute"
Test Projects and Base Classes
| Project | Scope | Base Class |
|---|
*.Domain.Tests | Domain logic, entity, domain service | *DomainTestBase |
*.Application.Tests | Application services | *ApplicationTestBase |
*.EntityFrameworkCore.Tests | Repository implementations | *EntityFrameworkCoreTestBase |
Each test gets a fresh database instance. Services are resolved via GetRequiredService<T>().
Application Service Test
public class BookAppService_Tests : MyProjectApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Create_Book()
{
var input = new CreateBookDto { Name = "New Book", Price = 19.99m };
var result = await _bookAppService.CreateAsync(input);
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New Book");
}
[Fact]
public async Task Should_Not_Create_Book_With_Invalid_Name()
{
var input = new CreateBookDto { Name = "", Price = 10m };
await Should.ThrowAsync<AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(input);
});
}
}
Domain Service Test
public class BookManager_Tests : MyProjectDomainTestBase
{
private readonly BookManager _bookManager;
public BookManager_Tests()
{
_bookManager = GetRequiredService<BookManager>();
}
[Fact]
public async Task Should_Not_Allow_Duplicate_Book_Name()
{
await _bookManager.CreateAsync("Existing Book", 10m);
var exception = await Should.ThrowAsync<BusinessException>(async () =>
{
await _bookManager.CreateAsync("Existing Book", 20m);
});
exception.Code.ShouldBe("MyProject:BookNameAlreadyExists");
}
}
Naming & AAA
public async Task Should_Throw_BusinessException_When_Name_Already_Exists() { }
[Fact]
public async Task Should_Update_Book_Price()
{
var bookId = await CreateTestBookAsync();
var result = await _bookAppService.UpdateAsync(bookId, new UpdateBookDto { Price = 39.99m });
result.Price.ShouldBe(39.99m);
}
Assertions (Shouldly)
ABP uses the Shouldly library:
result.ShouldNotBeNull();
result.Name.ShouldBe("Expected");
result.Price.ShouldBeGreaterThan(0);
result.Items.ShouldContain(x => x.Id == expectedId);
result.Items.ShouldBeEmpty();
var ex = await Should.ThrowAsync<BusinessException>(async () => await _service.DoAsync());
ex.Code.ShouldBe("MyProject:ErrorCode");
Test Data Seeding
public class MyProjectTestDataSeedContributor : IDataSeedContributor, ITransientDependency
{
public static readonly Guid TestBookId = Guid.Parse("....");
private readonly IBookRepository _bookRepository;
public MyProjectTestDataSeedContributor(IBookRepository bookRepository)
=> _bookRepository = bookRepository;
public async Task SeedAsync(DataSeedContext context)
{
await _bookRepository.InsertAsync(
new Book(TestBookId, "Test Book", 19.99m, Guid.Empty), autoSave: true);
}
}
Disabling Authorization in Tests
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAlwaysAllowAuthorization();
}
Mocking External Services (NSubstitute)
Don't mock internal ABP services — only external dependencies:
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 a Specific User / Tenant
using (CurrentUser.Change(TestData.UserId))
{
var result = await _bookAppService.GetMyBooksAsync();
result.Items.ShouldAllBe(b => b.CreatorId == TestData.UserId);
}
using (CurrentTenant.Change(TestData.TenantId))
{
var result = await _bookAppService.GetListAsync(new GetBookListDto());
}
Best Practices
- Prefer integration tests — real services + SQLite in-memory, don't mock internal services
- Keep each test independent — don't share state between tests
- Meaningful test data — use a seed contributor for shared data
- Test edge cases and error conditions — verify the exception
Code
- Focus on a single behavior —
Should_X_When_Y naming
- Don't test the framework internals
Related