| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /serverpod:test |
| Aliases | /backend:test, /api:test |
| Tools | Read, Edit, Write, Glob, Grep, Bash |
| Model | sonnet |
| Skills | serverpod, test |
/serverpod:test โ ๋ฐฑ์๋ ์๋ ๊ฒ์ฌ์ง ๋ง๋ค๊ธฐ#
Serverpod ๋ฐฑ์๋(์๋ฒ ์ชฝ ์ฝ๋)๊ฐ ์ ๋๋ก ์๋ํ๋์ง ํ์ธํ๋ "์๋ ๊ฒ์ฌ์ง(ํ ์คํธ ์ฝ๋)"๋ฅผ ๋์ ์์ฑํด ์ฃผ๋ ๋์ฐ๋ฏธ์ ๋๋ค.
ํ๋ง๋๋ก#
์๋ก ๋ง๋ ์๋ฒ ๊ธฐ๋ฅ์ด ์๋๋๋ก ๋์ํ๋์ง ๋งค๋ฒ ์์ผ๋ก ํ์ธํ์ง ์์๋ ๋๊ฒ, ์๋์ผ๋ก ๋๋ ค๋ณด๋ ๊ฒ์ฌ์ง(ํ ์คํธ ์ฝ๋) ๋ฅผ ๋ง๋ค์ด ์ค๋๋ค. ์ ์ ํ์ ์ถ๊ณ ํ๊ธฐ ์ ์ ํ์ง ๊ฒ์ฌ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์๋์ผ๋ก ์์ฑํด ์ฃผ๋ ๊ฒ๊ณผ ๊ฐ์์.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ์๋ฒ์ ์ ๊ธฐ๋ฅ(์๋ํฌ์ธํธ)์ ๋ง๋ ๋ค, ๊ทธ๊ฒ ์ ๋์๊ฐ๋์ง ๊ฒ์ฆํ๋ ํ ์คํธ๊ฐ ํ์ํ ๋
/feature:create(์ ์ฒด ๊ธฐ๋ฅ ๋ง๋ค๊ธฐ) ๊ณผ์ 3๋จ๊ณ์์ ์๋์ผ๋ก ํธ์ถ๋ฉ๋๋ค- ๋ก๊ทธ์ธ์ด ํ์ํ ๊ธฐ๋ฅ, ๊ด๋ฆฌ์๋ง ์ธ ์ ์๋ ๊ธฐ๋ฅ์ด ์ ๋๋ก ๋งํ ์๋์ง ํ์ธํ๊ณ ์ถ์ ๋
๋ฌด์์ ํด์ฃผ๋์#
ํ ์คํธ ์ฝ๋ ํ์ผ์ ์๋์ผ๋ก ๋ง๋ค์ด ์ค๋๋ค. ์ค์ ๋ก ์๊ธฐ๋ ํ์ผ์:
-
backend/kobic_server/test/integration/{๊ธฐ๋ฅ์ด๋ฆ}_endpoint_test.dartโ ํตํฉ ํ ์คํธ (์ค์ ์๋ฒ๋ฅผ ๋์ ์ ์ฒด ํ๋ฆ ํ์ธ) backend/kobic_server/test/unit/{๊ธฐ๋ฅ์ด๋ฆ}/{๊ธฐ๋ฅ์ด๋ฆ}_service_test.dartโ ๋จ์ ํ ์คํธ (๊ฐ๋ณ ๋ก์ง ํ์ธ)
๊ฒ์ฌ์ง์๋ ๋ณดํต ์ด๋ฐ ํญ๋ชฉ์ด ๋ด๊น๋๋ค: ๋ก๊ทธ์ธ ์์ด ํธ์ถํ๋ฉด ๋งํ๋์ง, ๊ถํ ์๋ ์ฌ์ฉ์๋ ๊ฑฐ๋ถ๋๋์ง, ๋ง๋ค๊ธฐยท์ฝ๊ธฐยท์์ ยท์ญ์ (CRUD)๊ฐ ์ ์ ๋์ํ๋์ง, ์๋ชป๋ ๊ฐ์ ๋ฃ์ผ๋ฉด ์ค๋ฅ๊ฐ ๋๋์ง ๋ฑ.
์ด๋ป๊ฒ ์ฐ๋์#
# ๊ธฐ๋ณธ (ํตํฉ ํ
์คํธ ์์ฑ)
/serverpod:test
# ๊ฐ์ ๊ธฐ๋ฅ์ ํ๋ ๋ค๋ฅธ ํธ์ถ ์ด๋ฆ(๋ณ์นญ)
/backend:test
/api:test
ํ
์คํธ ์ข
๋ฅ๋ integration(ํตํฉ), unit(๋จ์), both(๋ ๋ค) ์ค์์ ๊ณ ๋ฅผ ์ ์๊ณ , ๋ณ๋๋ก ์ง์ ํ์ง ์์ผ๋ฉด ํตํฉ ํ
์คํธ๊ฐ ๋ง๋ค์ด์ง๋๋ค.
๋ง๋ค์ด์ง ํ ์คํธ๋ฅผ ์ค์ ๋ก ๋๋ ค๋ณผ ๋๋:
# ํตํฉ ํ
์คํธ๋ง ์คํ
cd backend/kobic_server
dart test test/integration/
# ์ ์ฒด ํ
์คํธ ์คํ
dart test
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
- ์๋ก ๋ง๋ ์๋ฒ ๊ธฐ๋ฅ(์๋ํฌ์ธํธ) ์ฝ๋๋ฅผ ์ดํด๋ณด๊ณ , ์ด๋ค ํญ๋ชฉ์ ๊ฒ์ฌํด์ผ ํ ์ง ์ ๋ฆฌํฉ๋๋ค.
- ๋ก๊ทธ์ธ์ด ํ์ํ ๊ธฐ๋ฅ์ด๋ฉด "๋ก๊ทธ์ธ ์ ํ๋ฉด ๋งํ๋์ง" ๊ฒ์ฌ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
- ๊ด๋ฆฌ์ ๊ถํ์ด ํ์ํ ๊ธฐ๋ฅ์ด๋ฉด "์ผ๋ฐ ์ฌ์ฉ์๋ ๊ฑฐ๋ถ๋๋์ง" ๊ฒ์ฌ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
- ๋ง๋ค๊ธฐยท์ฝ๊ธฐยท์์ ยท์ญ์ (CRUD)๊ฐ ์ ๋๋ก ๋๋์ง, ์๋ชป๋ ์ ๋ ฅ์ ์ค๋ฅ๊ฐ ๋๋์ง ๊ฒ์ฌ๋ฅผ ๋ํฉ๋๋ค.
- ์ด ๋ชจ๋ ํญ๋ชฉ์ ํ๋์ ํ ์คํธ ํ์ผ๋ก ๋ง๋ค์ด ์ค๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
Role#
Generates test code for the Serverpod backend.
- Integration tests (using
withServerpodhelper) - Unit tests (service logic)
- Authentication/authorization tests
- Error case tests
Activation Conditions#
- Activated on
/serverpod:testcommand invocation - Called in Step 3 of
/feature:createorchestration - Called when writing tests after endpoint creation
Parameters#
| Parameter | Required | Description |
|---|---|---|
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 testsImport 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#
Serverpod 4 local full-stack note: With Serverpod 4 (tech preview), the bundled embedded Postgres lets integration tests run fully locally without Docker. Run
serverpod start(which boots the backend server + embedded Postgres + Flutter app with sub-second hot reload), then rundart test test/integration/(ordart test -t integration) directly. Nodocker compose up -dneeded. See serverpod-local-fullstack. Thedocker compose up -dpath below is preserved as the v3 / legacy flow (kobic production is on 3.x).
# (3.x / legacy) 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=coverageTest 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#
| Case | Description |
|---|---|
| Unauthenticated call | Verify ServerpodUnauthenticatedException is thrown |
| Insufficient permissions | Verify ServerpodInsufficientAccessException is thrown |
| Normal CRUD | Verify Create, Read, Update, Delete success |
| Lookup failure | Verify NotFoundException is thrown |
| Validation error | Verify ValidationException is thrown |
| Paging validation | Verify limit, offset parameter behavior |
Additional Test Cases (Optional)#
| Case | Description |
|---|---|
| Transaction rollback | Verify DB changes are rolled back |
| Concurrency | Verify concurrent request handling |
| Cache behavior | Verify Redis cache application |
| External service integration | Use 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#
| Option | Description |
|---|---|
afterEach | Rollback after each test (default, recommended) |
afterAll | Rollback after all tests |
disabled | Disable rollback (use with caution) |
Checklist#
- Follow import order
- Use
withServerpodhelper - 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