Skip to main content

id: testing

Testing in SophiChain

ABP Framework Testing Patterns


id: testing

๐Ÿ“– Overviewโ€‹

SophiChain uses ABP's testing infrastructure with xUnit, Shouldly, and built-in test base classes.


id: testing

๐Ÿ—๏ธ Test Structureโ€‹

Application Testsโ€‹

public class WalletAppService_Tests : FinanceHubApplicationTestBase
{
private readonly IWalletAppService _walletAppService;
private readonly IWalletRepository _walletRepository;

public WalletAppService_Tests()
{
_walletAppService = GetRequiredService<IWalletAppService>();
_walletRepository = GetRequiredService<IWalletRepository>();
}

[Fact]
public async Task Should_Create_Wallet()
{
await WithUnitOfWorkAsync(async () =>
{
// Arrange
var input = new CreateWalletDto
{
UserId = Guid.NewGuid(),
Name = "Test Wallet"
};

// Act
var result = await _walletAppService.CreateAsync(input);

// Assert
result.ShouldNotBeNull();
result.Name.ShouldBe("Test Wallet");
});
}
}

id: testing

๐ŸŽฏ Test Patternsโ€‹

Arrange-Act-Assertโ€‹

[Fact]
public async Task Should_Update_Wallet()
{
await WithUnitOfWorkAsync(async () =>
{
// Arrange
var wallet = new Wallet(Guid.NewGuid(), Guid.NewGuid(), "Original");
await _repository.InsertAsync(wallet);

// Act
await _service.UpdateAsync(wallet.Id, new UpdateDto { Name = "Updated" });

// Assert
var updated = await _repository.GetAsync(wallet.Id);
updated.Name.ShouldBe("Updated");
});
}

Testing with Shouldlyโ€‹

// Equality
result.ShouldBe(expected);
result.ShouldNotBe(unexpected);

// Null checks
result.ShouldBeNull();
result.ShouldNotBeNull();

// Collections
list.ShouldContain(item);
list.ShouldNotContain(item);
list.ShouldAllBe(x => x.IsActive);

// Numeric
count.ShouldBeGreaterThan(0);
amount.ShouldBeLessThanOrEqualTo(100);

// Boolean
isActive.ShouldBeTrue();
isDeleted.ShouldBeFalse();

id: testing

๐Ÿงช Test Base Classesโ€‹

Application Test Baseโ€‹

public abstract class MyModuleApplicationTestBase<TStartupModule> 
: MyModuleTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
// Inherits:
// - WithUnitOfWorkAsync()
// - GetRequiredService<T>()
// - GetService<T>()
}

Domain Test Baseโ€‹

public abstract class MyModuleDomainTestBase<TStartupModule> 
: MyModuleTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
// Test domain logic and entities
}

id: testing

โš™๏ธ Test Configurationโ€‹

Test Moduleโ€‹

[DependsOn(
typeof(MyModuleApplicationModule),
typeof(AbpTestBaseModule)
)]
public class MyModuleApplicationTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
});

// Bypass authorization for tests
context.Services.AddAlwaysAllowAuthorization();
}
}

id: testing

๐ŸŽจ Common Test Scenariosโ€‹

Test Paginationโ€‹

[Fact]
public async Task Should_Paginate_Results()
{
await WithUnitOfWorkAsync(async () =>
{
// Arrange: Create 5 items
for (int i = 0; i < 5; i++)
await _repository.InsertAsync(new Item());

// Act
var result = await _service.GetListAsync(new PagedInput
{
SkipCount = 0,
MaxResultCount = 3
});

// Assert
result.TotalCount.ShouldBeGreaterThanOrEqualTo(5);
result.Items.Count.ShouldBeLessThanOrEqualTo(3);
});
}

Test Filteringโ€‹

[Fact]
public async Task Should_Filter_By_User()
{
await WithUnitOfWorkAsync(async () =>
{
var userId = Guid.NewGuid();
await _repository.InsertAsync(new Item { UserId = userId });
await _repository.InsertAsync(new Item { UserId = Guid.NewGuid() });

var result = await _service.GetListAsync(new GetInput { UserId = userId });

result.Items.ShouldAllBe(x => x.UserId == userId);
});
}

Test Business Logicโ€‹

[Fact]
public async Task Should_Throw_When_Insufficient_Balance()
{
var wallet = new Wallet();

await Should.ThrowAsync<BusinessException>(async () =>
{
wallet.Debit(100); // Balance is 0
});
}

id: testing

โœ… Best Practicesโ€‹

Test Namingโ€‹

  • โœ… Use Should_Expected_Behavior pattern
  • โœ… Be descriptive: Should_Create_Wallet_With_Initial_Balance
  • โŒ Don't use generic names: Test1, TestMethod

Test Structureโ€‹

  • โœ… One assertion concept per test
  • โœ… Use Arrange-Act-Assert pattern
  • โœ… Keep tests independent
  • โœ… Use WithUnitOfWorkAsync for database operations
  • โŒ Don't depend on test execution order

Test Dataโ€‹

  • โœ… Create test data in each test
  • โœ… Use helper methods for common setup
  • โœ… Clean up is automatic (transaction rollback)
  • โŒ Don't rely on seed data

Assertionsโ€‹

  • โœ… Use Shouldly for readable assertions
  • โœ… Assert specific values, not just "not null"
  • โœ… Test edge cases
  • โŒ Don't use multiple unrelated assertions

id: testing

๐Ÿ“– Referencesโ€‹


id: testing