Smithery Logo
MCPsSkillsDocsPricing
Login
NewFlame, an assistant that learns and improves. Available onTelegramSlack
    thapaliyabikendra

    xunit-testing-patterns

    thapaliyabikendra/xunit-testing-patterns
    Coding
    4

    About

    SKILL.md

    Install

    • Telegram
      Telegram
    • Slack
      Slack
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    • Download skill
    ├─
    ├─
    └─
    Smithery Logo

    Give agents more agency

    Resources

    DocumentationPrivacy PolicySystem Status

    Company

    PricingAboutBlog

    Connect

    © 2026 Smithery. All rights reserved.

    About

    Master xUnit testing patterns for ABP Framework applications including unit tests, integration tests, test data seeders, and mocking strategies...

    SKILL.md

    xUnit Testing Patterns for ABP Framework

    Comprehensive testing patterns for ABP Framework applications using xUnit, Shouldly, and NSubstitute.

    When to Use

    • Writing unit tests for AppServices
    • Creating integration tests for ABP modules
    • Setting up test data seeders
    • Mocking repositories and services
    • Testing authorization and validation
    • Writing domain service tests
    • Interface-first testing (writing tests before implementation)

    Test Project Structure

    {ProjectName}.TestBase/
    ├── {ProjectName}TestBase.cs           # Base class with common setup
    ├── {ProjectName}TestBaseModule.cs     # Test module configuration
    └── {Feature}/
        ├── {Entity}TestData.cs            # Test constants
        └── {Entity}TestDataSeedContributor.cs  # Test data seeder
    
    {ProjectName}.Application.Tests/
    ├── {ProjectName}ApplicationTestBase.cs  # Application test base
    ├── {ProjectName}ApplicationTestModule.cs
    └── {Feature}/
        └── {Entity}AppService_Tests.cs    # AppService tests
    
    {ProjectName}.Domain.Tests/
    ├── {ProjectName}DomainTestBase.cs     # Domain test base
    ├── {ProjectName}DomainTestModule.cs
    └── {Feature}/
        └── {Entity}Manager_Tests.cs       # Domain service tests
    

    Interface-First Testing (NEW)

    Write tests against interfaces before implementation exists. This enables parallel development in /add-feature workflow.

    Benefits

    1. Tests can be written as soon as interface contracts exist
    2. Enables true parallel execution of abp-developer and qa-engineer
    3. Tests document expected behavior
    4. Catches interface design issues early

    Example: Testing Against Interface

    // This test compiles and is ready to run once implementation exists
    public class PatientAppService_Tests : ClinicApplicationTestBase
    {
        private readonly IPatientAppService _patientAppService;
    
        public PatientAppService_Tests()
        {
            // Resolves implementation from DI container
            _patientAppService = GetRequiredService<IPatientAppService>();
        }
    
        [Fact]
        public async Task GetAsync_WithValidId_ReturnsPatient()
        {
            // Arrange - uses test data constants
            var patientId = PatientTestData.Patient1Id;
    
            // Act - calls interface method
            var result = await _patientAppService.GetAsync(patientId);
    
            // Assert - validates contract expectations
            result.ShouldNotBeNull();
            result.Id.ShouldBe(patientId);
            result.FirstName.ShouldBe(PatientTestData.Patient1FirstName);
        }
    }
    

    Core Templates

    Test Data Constants

    // {ProjectName}.TestBase/{Feature}/{Entity}TestData.cs
    namespace {ProjectName}.{Feature};
    
    public static class PatientTestData
    {
        // Use deterministic GUIDs for test reproducibility
        public static Guid Patient1Id { get; } = Guid.Parse("00000000-0000-0000-0001-000000000001");
        public static Guid Patient2Id { get; } = Guid.Parse("00000000-0000-0000-0001-000000000002");
        public static Guid NonExistentId { get; } = Guid.Parse("00000000-0000-0000-0001-999999999999");
    
        // Valid test data
        public const string Patient1FirstName = "John";
        public const string Patient1LastName = "Doe";
        public const string Patient1Email = "john.doe@example.com";
    
        public const string Patient2FirstName = "Jane";
        public const string Patient2LastName = "Smith";
        public const string Patient2Email = "jane.smith@example.com";
    
        // Valid data for create tests
        public const string ValidFirstName = "New";
        public const string ValidLastName = "Patient";
        public const string ValidEmail = "new.patient@example.com";
    
        // Invalid data for negative tests
        public const string EmptyString = "";
        public const string WhitespaceString = "   ";
        public static readonly string TooLongName = new('X', 256);
        public const string InvalidEmail = "not-an-email";
    }
    

    Test Data Seeder

    // {ProjectName}.TestBase/{Feature}/{Entity}TestDataSeedContributor.cs
    using System;
    using System.Threading.Tasks;
    using Volo.Abp.Data;
    using Volo.Abp.DependencyInjection;
    using Volo.Abp.Domain.Repositories;
    
    namespace {ProjectName}.{Feature};
    
    public class PatientTestDataSeedContributor : IDataSeedContributor, ITransientDependency
    {
        private readonly IRepository<Patient, Guid> _repository;
    
        public PatientTestDataSeedContributor(IRepository<Patient, Guid> repository)
        {
            _repository = repository;
        }
    
        public async Task SeedAsync(DataSeedContext context)
        {
            // Idempotent seeding
            if (await _repository.GetCountAsync() > 0)
            {
                return;
            }
    
            // Patient 1 - Active
            await _repository.InsertAsync(
                new Patient(
                    PatientTestData.Patient1Id,
                    PatientTestData.Patient1FirstName,
                    PatientTestData.Patient1LastName,
                    PatientTestData.Patient1Email),
                autoSave: true);
    
            // Patient 2 - For deletion/update tests
            await _repository.InsertAsync(
                new Patient(
                    PatientTestData.Patient2Id,
                    PatientTestData.Patient2FirstName,
                    PatientTestData.Patient2LastName,
                    PatientTestData.Patient2Email),
                autoSave: true);
        }
    }
    

    AppService Test Class

    For full test class template with all CRUD operations, lifecycle tests, and mocking patterns: See references/appservice-test-template.md

    Quick example:

    [Trait("Category", "Integration")]
    public class {Entity}AppService_Tests : {ProjectName}ApplicationTestBase
    {
        private readonly I{Entity}AppService _{entity}AppService;
    
        public {Entity}AppService_Tests()
        {
            _{entity}AppService = GetRequiredService<I{Entity}AppService>();
        }
    
        [Fact]
        public async Task GetAsync_WithValidId_Returns{Entity}()
        {
            var result = await _{entity}AppService.GetAsync({Entity}TestData.{Entity}1Id);
            result.ShouldNotBeNull();
            result.Id.ShouldBe({Entity}TestData.{Entity}1Id);
        }
    
        [Fact]
        public async Task CreateAsync_WithValidInput_CreatesAndReturns{Entity}()
        {
            var input = new Create{Entity}Dto { Name = {Entity}TestData.ValidName };
            var result = await _{entity}AppService.CreateAsync(input);
            result.ShouldNotBeNull();
            result.Id.ShouldNotBe(Guid.Empty);
        }
    }
    

    Test Categories

    1. Happy Path Tests

    Test normal successful operations.

    [Fact]
    public async Task Should_Create_Entity_Successfully()
    {
        // Standard create with valid data
    }
    

    2. Validation Tests

    Test input validation and constraints.

    [Theory]
    [InlineData("")]
    [InlineData(null)]
    [InlineData("   ")]
    public async Task Should_Reject_Invalid_Name(string? name)
    {
        var input = new CreateDto { Name = name! };
        await Should.ThrowAsync<AbpValidationException>(
            () => _service.CreateAsync(input));
    }
    

    3. Authorization Tests

    Test permission enforcement.

    [Fact]
    public async Task Should_Require_Permission_To_Create()
    {
        // Login as user without permission
        await WithUnitOfWorkAsync(async () =>
        {
            await Should.ThrowAsync<AbpAuthorizationException>(
                () => _service.CreateAsync(input));
        });
    }
    

    4. Edge Case Tests

    Test boundary conditions and edge cases.

    [Fact]
    public async Task Should_Handle_Empty_List()
    {
        // Clear all data
        var result = await _service.GetListAsync(new GetListInput());
        result.TotalCount.ShouldBe(0);
        result.Items.ShouldBeEmpty();
    }
    
    [Fact]
    public async Task Should_Handle_Max_Page_Size()
    {
        var result = await _service.GetListAsync(
            new GetListInput { MaxResultCount = 1000 });
        result.Items.Count.ShouldBeLessThanOrEqualTo(100); // Capped
    }
    

    5. Lifecycle Tests (for Activate/Deactivate patterns)

    See references/appservice-test-template.md for full lifecycle test examples.

    Test Traits for Organization

    // Categorize tests for selective execution
    [Trait("Category", "Unit")]
    [Trait("Feature", "Patients")]
    public class PatientAppService_UnitTests { }
    
    [Trait("Category", "Integration")]
    [Trait("Feature", "Patients")]
    public class PatientAppService_IntegrationTests { }
    
    // Run by category:
    // dotnet test --filter "Category=Unit"
    // dotnet test --filter "Feature=Patients"
    

    Mocking with NSubstitute

    using NSubstitute;
    
    // Create mock
    var repository = Substitute.For<IRepository<{Entity}, Guid>>();
    
    // Setup return value
    repository.GetAsync(entityId).Returns(entity);
    
    // Verify call
    await repository.Received(1).GetAsync(entityId);
    

    For full mocking examples, see references/appservice-test-template.md.

    Shouldly Assertion Patterns

    // Null checks
    result.ShouldNotBeNull();
    result.ShouldBeNull();
    
    // Equality
    result.Id.ShouldBe(expectedId);
    result.Name.ShouldNotBe(oldName);
    
    // Collections
    result.Items.ShouldNotBeEmpty();
    result.Items.ShouldContain(x => x.Name == "Test");
    result.Items.Count.ShouldBe(5);
    result.Items.ShouldAllBe(x => x.IsActive);
    
    // Numeric comparisons
    result.TotalCount.ShouldBeGreaterThan(0);
    result.TotalCount.ShouldBeLessThanOrEqualTo(100);
    result.TotalCount.ShouldBeInRange(1, 100);
    
    // String assertions
    result.Name.ShouldStartWith("Test");
    result.Email.ShouldContain("@");
    result.Name.ShouldNotBeNullOrWhiteSpace();
    
    // Boolean assertions
    result.IsActive.ShouldBeTrue();
    result.IsDeleted.ShouldBeFalse();
    
    // Exception assertions
    await Should.ThrowAsync<EntityNotFoundException>(
        async () => await _service.GetAsync(invalidId));
    
    var ex = await Should.ThrowAsync<BusinessException>(
        async () => await _service.CreateAsync(input));
    ex.Code.ShouldBe("DuplicateEmail");
    

    Parallel Test Safety

    When tests run in parallel, ensure data isolation:

    // Use unique IDs per test class
    public static class PatientTestData
    {
        // Include feature identifier in GUIDs to avoid collisions
        private const string FeaturePrefix = "00000000-0000-0001";
    
        public static Guid Patient1Id { get; } = Guid.Parse($"{FeaturePrefix}-0001-000000000001");
        public static Guid Patient2Id { get; } = Guid.Parse($"{FeaturePrefix}-0001-000000000002");
    }
    

    Test Checklist

    For each AppService, verify:

    • GetAsync - valid ID returns entity
    • GetAsync - non-existent ID throws EntityNotFoundException
    • GetListAsync - returns paginated results
    • GetListAsync - respects filters
    • GetListAsync - respects pagination
    • CreateAsync - valid input creates entity
    • CreateAsync - empty required field throws validation
    • CreateAsync - exceeds max length throws validation
    • UpdateAsync - valid input updates entity
    • UpdateAsync - non-existent ID throws EntityNotFoundException
    • DeleteAsync - valid ID deletes entity
    • DeleteAsync - non-existent ID throws EntityNotFoundException
    • (If applicable) ActivateAsync - activates inactive entity
    • (If applicable) DeactivateAsync - deactivates active entity

    Shared Knowledge

    For foundational patterns, see the shared knowledge base:

    Topic File Description
    Folder structure knowledge/conventions/folder-structure.md Test project layout
    Naming conventions knowledge/conventions/naming.md Test class naming
    CRUD example knowledge/examples/crud-entity.md Test target example

    References

    • references/integration-test-patterns.md - Advanced integration testing
    • references/test-fixtures.md - Shared test fixtures
    Recommended Servers
    bugAgent
    bugAgent
    Nimble MCP Server
    Nimble MCP Server
    DataForB2B
    DataForB2B
    Repository
    thapaliyabikendra/ai-artifacts
    Files