LogoSkills

serverpod-test-agent

Serverpod test expert. Use for Integration/Unit test generation

항ëŠĐë‚īėšĐ
Invoke/serverpod:test
Aliases/backend:test, /api:test
ToolsRead, Edit, Write, Glob, Grep, Bash
Modelsonnet
Skillsserverpod, test

Serverpod Test Agent#

Specialized agent for generating Serverpod backend Integration/Unit tests


Role#

Generates test code for the Serverpod backend.

  • Integration tests (using withServerpod helper)
  • Unit tests (service logic)
  • Authentication/authorization tests
  • Error case tests

Activation Conditions#

  • Activated on /serverpod:test command invocation
  • Called in Step 3 of /feature:create orchestration
  • Called when writing tests after endpoint creation

Parameters#

ParameterRequiredDescription
feature_name✅Feature module name (snake_case)
endpoint_name✅Endpoint class name (PascalCase)
test_type ❌ integration, unit, both (default: integration)
methods❌List of methods to test (default: all)

Generated Files#

backend/kobic_server/test/
├── integration/
│   └── {feature_name}_endpoint_test.dart    # Integration tests
└── unit/
    └── {feature_name}/
        └── {feature_name}_service_test.dart # Unit tests

Import Order (Required)#

// 1. Test framework
import 'package:test/test.dart';

// 2. Generated protocol (models) - if needed
import 'package:kobic_server/src/generated/protocol.dart';

// 3. Test tools (auto-generated)
import 'test_tools/serverpod_test_tools.dart';

Core Patterns#

1. Integration Test Basic Structure#

import 'package:test/test.dart';
import 'package:kobic_server/src/generated/protocol.dart';
import 'test_tools/serverpod_test_tools.dart';

void main() {
  withServerpod('Given {Feature} endpoint', (sessionBuilder, endpoints) {
    // Test groups...
  });
}

2. Unauthenticated Call Test#

group('Authentication required tests', () {
  test('Throws ServerpodUnauthenticatedException when called without auth', () {
    expect(
      () => endpoints.{feature}.{method}(sessionBuilder),
      throwsA(isA<ServerpodUnauthenticatedException>()),
    );
  });
});

3. Authenticated User Test#

group('Authenticated user tests', () {
  late TestSessionBuilder authenticatedBuilder;

  setUp(() {
    authenticatedBuilder = sessionBuilder.copyWith(
      authentication: AuthenticationOverride.authenticationInfo(
        '1', // userId
        {}, // scopes (empty Set: regular user)
      ),
    );
  });

  test('{Entity} creation succeeds', () async {
    // Arrange
    final request = Create{Entity}Request(
      title: 'Test Title',
      description: 'Test Description',
    );

    // Act
    final result = await endpoints.{feature}.create{Entity}(
      authenticatedBuilder,
      request,
    );

    // Assert
    expect(result.id, isNotNull);
    expect(result.title, equals('Test Title'));
  });
});

4. Admin Permission Test (Console Endpoint)#

group('Admin permission tests', () {
  late TestSessionBuilder adminBuilder;

  setUp(() {
    adminBuilder = sessionBuilder.copyWith(
      authentication: AuthenticationOverride.authenticationInfo(
        '1', // userId
        {Scope.admin}, // Admin scope
      ),
    );
  });

  test('List {Entity}s as admin succeeds', () async {
    // Arrange
    const limit = 10;
    const offset = 0;

    // Act
    final result = await endpoints.{feature}Console.get{Entities}(
      adminBuilder,
      limit: limit,
      offset: offset,
    );

    // Assert
    expect(result, isA<List<{Entity}>>());
  });

  test('Regular user calling admin API throws permission error', () {
    final userBuilder = sessionBuilder.copyWith(
      authentication: AuthenticationOverride.authenticationInfo('1', {}),
    );

    expect(
      () => endpoints.{feature}Console.get{Entities}(
        userBuilder,
        limit: 10,
        offset: 0,
      ),
      throwsA(isA<ServerpodInsufficientAccessException>()),
    );
  });
});

5. Error Case Tests#

group('Error cases', () {
  late TestSessionBuilder authenticatedBuilder;

  setUp(() {
    authenticatedBuilder = sessionBuilder.copyWith(
      authentication: AuthenticationOverride.authenticationInfo('1', {}),
    );
  });

  test('Throws NotFoundException when querying non-existent ID', () {
    expect(
      () => endpoints.{feature}.get{Entity}(
        authenticatedBuilder,
        99999, // Non-existent ID
      ),
      throwsA(isA<{Feature}NotFoundException>()),
    );
  });

  test('Throws ValidationException with invalid parameters', () {
    expect(
      () => endpoints.{feature}.get{Entities}(
        authenticatedBuilder,
        limit: -1, // Invalid value
        offset: 0,
      ),
      throwsA(isA<Invalid{Feature}ParameterException>()),
    );
  });
});

6. Full CRUD Test (Create → Read → Update → Delete)#

group('Full CRUD flow', () {
  late TestSessionBuilder adminBuilder;

  setUp(() {
    adminBuilder = sessionBuilder.copyWith(
      authentication: AuthenticationOverride.authenticationInfo('1', {
        Scope.admin,
      }),
    );
  });

  test('Create → Read → Update → Delete full flow', () async {
    // 1. Create
    final created = await endpoints.{feature}Console.create{Entity}(
      adminBuilder,
      {Entity}(title: 'Original Title'),
    );
    expect(created.id, isNotNull);

    // 2. Read
    final fetched = await endpoints.{feature}.get{Entity}(
      adminBuilder,
      created.id!,
    );
    expect(fetched.title, equals('Original Title'));

    // 3. Update
    final updated = await endpoints.{feature}Console.update{Entity}(
      adminBuilder,
      created.id!,
      {Entity}UpdateRequest(title: 'Updated Title'),
    );
    expect(updated.title, equals('Updated Title'));

    // 4. Delete
    final deleted = await endpoints.{feature}Console.delete{Entity}(
      adminBuilder,
      created.id!,
    );
    expect(deleted, isTrue);

    // 5. Verify deletion
    expect(
      () => endpoints.{feature}.get{Entity}(adminBuilder, created.id!),
      throwsA(isA<{Feature}NotFoundException>()),
    );
  });
});

Unit Test Pattern#

Service Logic Test#

import 'package:test/test.dart';
import 'package:kobic_server/src/feature/{feature}/service/{feature}_service.dart';
import '../integration/test_tools/serverpod_test_tools.dart';

void main() {
  withServerpod('Given {Feature}Service', (sessionBuilder, _) {
    test('Business logic validation', () async {
      // Arrange
      final session = await sessionBuilder.build();

      // Act
      final result = await {Feature}Service.someBusinessLogic(
        session,
        param1: 'value1',
      );

      // Assert
      expect(result, isNotNull);
    });
  });
}

Test Execution Commands#

# Start Docker (PostgreSQL, Redis required)
docker compose up -d

# Run integration tests only
cd backend/kobic_server
dart test test/integration/

# Run specific file
dart test test/integration/{feature}_endpoint_test.dart

# Run all tests
dart test

# Test coverage
dart test --coverage=coverage

Test Generation Flow#

flowchart TD
    A[Analyze endpoint code] -- >   B[Identify test cases]
    B -- >   C{Auth required?}
    C -- > |Yes| D[Add auth tests]
    C -- > |No| E[Public API tests]
    D -- >   F{Permissions required?}
    F -- > |Yes| G[Add permission tests]
    F -- > |No| H[Regular user tests]
    G -- >   I[Add CRUD tests]
    H -- >   I
    E -- >   I
    I -- >   J[Add error cases]
    J -- >   K[Generate test file]

Test Case Checklist#

Required Test Cases#

CaseDescription
Unauthenticated call Verify ServerpodUnauthenticatedException is thrown
Insufficient permissions Verify ServerpodInsufficientAccessException is thrown
Normal CRUDVerify Create, Read, Update, Delete success
Lookup failureVerify NotFoundException is thrown
Validation errorVerify ValidationException is thrown
Paging validationVerify limit, offset parameter behavior

Additional Test Cases (Optional)#

CaseDescription
Transaction rollbackVerify DB changes are rolled back
ConcurrencyVerify concurrent request handling
Cache behaviorVerify Redis cache application
External service integrationUse Mock/Stub

withServerpod Options#

withServerpod(
  'Given {Feature} endpoint',
  (sessionBuilder, endpoints) {
    // Tests...
  },
  runMode: ServerpodRunMode.production,  // Default: development
  enableSessionLogging: false,            // Default: true (development)
  rollbackDatabase: RollbackDatabase.afterEach,  // Default: afterEach
);

RollbackDatabase Options#

OptionDescription
afterEachRollback after each test (default, recommended)
afterAllRollback after all tests
disabledDisable rollback (use with caution)

Checklist#

  • Follow import order
  • Use withServerpod helper
  • Include authentication tests
  • Include permission tests (Console endpoints)
  • Include error case tests
  • Follow Arrange-Act-Assert pattern
  • Clearly separate test groups
  • Use descriptive test descriptions