Backend Testing Standard
This skill defines the standard for writing backend unit tests in this project. Goal: Isolate tests, enable parallel execution, and focus on business logic verification.
Core Rules
1. ❌ PROHIBITED: Direct Database Schema Modification
- •DO NOT use
sequelize.sync({ force: true }). - •DO NOT use
Model.sync(). - •DO NOT use raw SQL to
DROPorCREATEtables/schemas in tests. - •Reason: These operations destroy the shared database state, causing race conditions in parallel tests.
2. ✅ REQUIRED: Use Mocks for Data
- •DO NOT rely on pre-seeded database data.
- •DO NOT rely on other tests' side effects.
- •MOCK the database calls (e.g.,
Model.findOne,Model.create) usingvi.mock(Vitest) orjest.mock. - •Reason: Tests must be deterministic and independent.
3. ✅ REQUIRED: Derive Mock Data from Specs & Migrations
- •DO NOT guess the data structure.
- •REFER TO
docs/specand database migrations (or Sequelize Model definitions) to ensure your mock JSON objects match the actual data structure found in production. - •Reason: Discrepancies between mock data and real schema lead to false positives/negatives.
3. ✅ REQUIRED: Focus on Business Logic
- •Test the Service Layer and Controller Layer logic.
- •Verify that:
- •Inputs are validated correctly.
- •Logic branches (if/else) are covered.
- •Correct Service/Model methods are called with expected arguments.
- •Proper Responses/Errors are returned.
Decision Tree
code
Testing a Feature?
├─ Is it pure logic? (e.g. calculation)
│ └─ Write a simple Unit Test (no database needed).
│
└─ Does it involve DB operations?
└─ Mock the Model methods.
├─ `User.findOne` -> return fake user
├─ `Transaction.create` -> return fake transaction
└─ Verify your function handles the return value correctly.
Setup & Examples
Mocking with Vitest
See examples/mock_test_example.ts for a complete example.
Common Pattern
typescript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import * as transactionService from '@/services/transactionServices';
import Transaction from '@/models/transaction';
// 1. Mock the specific model methods
vi.mock('@/models/transaction', () => ({
default: {
create: vi.fn(),
findOne: vi.fn(),
},
}));
describe('Transaction Service', () => {
beforeEach(() => {
vi.clearAllMocks(); // Reset call counts
});
it('should create transaction with correct data', async () => {
// 2. Define Mock Return Value
const fakeTx = { id: '1', amount: 100 };
(Transaction.create as any).mockResolvedValue(fakeTx);
// 3. Call the function under test
const result = await transactionService.createTransaction({ amount: 100 });
// 4. Verify Behavior
expect(Transaction.create).toHaveBeenCalledWith(
expect.objectContaining({ amount: 100 })
);
expect(result).toEqual(fakeTx);
});
});
Migration Guide (Fixing Old Tests)
If you encounter a test using sync({ force: true }):
- •Remove the
synccall. - •Identify which Models are being accessed.
- •Mock those Models at the top of the test file.
- •Replace real DB calls with
mockResolvedValue.