# 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.

- Skill: `majiayu000/abp-testing` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/abp-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/abp-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/abp-testing

---


# 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

