Skip to main content

Testing Strategy

Testing approach and patterns for the CRM platform.

Test Pyramid

                    ┌───────────────┐
│ E2E Tests │ (few, slow)
│ Playwright │
└───────────────┘
┌─────────────────────┐
│ Integration Tests │ (moderate)
│ Testcontainers │
└─────────────────────┘
┌───────────────────────────────────────┐
│ Unit Tests │ (many, fast)
│ Vitest │
└───────────────────────────────────────┘

Unit Tests

Tools

  • Framework: Vitest
  • Mocking: vi.mock, vi.fn

What to Unit Test

  • Service business logic
  • Utility functions
  • DTOs and validation
  • Guard and interceptor logic

Example

describe('IdentityResolutionService', () => {
let service: IdentityResolutionService;
let mockPrisma: DeepMockProxy<PrismaClient>;

beforeEach(() => {
mockPrisma = mockDeep<PrismaClient>();
service = new IdentityResolutionService(mockPrisma);
});

it('should match by email', async () => {
const existingProfile = { id: '123', email: 'test@example.com' };
mockPrisma.customerProfile.findFirst.mockResolvedValue(existingProfile);

const result = await service.resolve({
tenantId: 'tenant-1',
email: 'test@example.com',
});

expect(result).toEqual(existingProfile);
expect(mockPrisma.customerProfile.findFirst).toHaveBeenCalledWith({
where: { tenantId: 'tenant-1', email: 'test@example.com' },
});
});

it('should create new profile when no match', async () => {
mockPrisma.customerProfile.findFirst.mockResolvedValue(null);
mockPrisma.customerProfile.create.mockResolvedValue({
id: 'new-123',
email: 'new@example.com',
});

const result = await service.resolveOrCreate({
tenantId: 'tenant-1',
email: 'new@example.com',
});

expect(result.id).toBe('new-123');
expect(mockPrisma.customerProfile.create).toHaveBeenCalled();
});
});

Integration Tests

Tools

  • Framework: Vitest
  • Database: Testcontainers (PostgreSQL)
  • Queue: Testcontainers (Redis)

What to Integration Test

  • Database queries (Prisma)
  • Queue processing (BullMQ)
  • Cross-service interactions

Example

describe('CustomerProfileService (integration)', () => {
let container: StartedPostgreSqlContainer;
let prisma: PrismaClient;
let service: CustomerProfileService;

beforeAll(async () => {
container = await new PostgreSqlContainer().start();
prisma = new PrismaClient({
datasources: { db: { url: container.getConnectionUri() } },
});
await prisma.$executeRaw`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
// Run migrations...
service = new CustomerProfileService(prisma);
});

afterAll(async () => {
await prisma.$disconnect();
await container.stop();
});

it('should create and retrieve profile', async () => {
const profile = await service.create({
tenantId: 'test-tenant',
email: 'test@example.com',
firstName: 'Test',
});

const retrieved = await service.findById(profile.id);

expect(retrieved).toEqual(profile);
});
});

E2E Tests

Tools

  • Framework: Playwright
  • API Testing: Built-in request context

What to E2E Test

  • Critical user flows
  • API contract validation
  • Auth flows

Example

test('create and execute campaign', async ({ request }) => {
// Create segment
const segmentResponse = await request.post('/api/v1/segments', {
data: {
name: 'Test Segment',
type: 'DYNAMIC',
rules: [{ field: 'status', operator: 'eq', value: 'ACTIVE' }],
},
});
const segment = await segmentResponse.json();

// Create campaign
const campaignResponse = await request.post('/api/v1/campaigns', {
data: {
name: 'Test Campaign',
type: 'ONE_TIME',
segmentId: segment.data.id,
channels: ['EMAIL'],
},
});
const campaign = await campaignResponse.json();

expect(campaign.data.status).toBe('DRAFT');

// Schedule campaign
await request.post(`/api/v1/campaigns/${campaign.data.id}/schedule`);

// Verify status
const updated = await request.get(`/api/v1/campaigns/${campaign.data.id}`);
const updatedData = await updated.json();

expect(updatedData.data.status).toBe('SCHEDULED');
});

Running Tests

# Unit tests
npx nx test crm-core
npx nx test crm-campaigns

# Integration tests
npx nx test crm-backend --configuration=integration

# E2E tests
npx nx e2e crm-backend-e2e

# All tests with coverage
npx nx test crm-core --coverage

Test Fixtures

Factory Pattern

export const customerProfileFactory = {
build: (overrides?: Partial<CustomerProfile>): CustomerProfile => ({
id: faker.string.uuid(),
tenantId: 'test-tenant',
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
status: 'ACTIVE',
lifecycleStage: 'CUSTOMER',
...overrides,
}),
};

Mocking External Services

Messaging Service

const mockMessagingClient = {
upsertSegment: vi.fn().mockResolvedValue({ id: 'msg-segment-1' }),
sendCampaign: vi.fn().mockResolvedValue({ success: true }),
};

Social APIs

const mockFacebookClient = {
publishPost: vi.fn().mockResolvedValue({ id: 'fb-post-123' }),
getEngagement: vi.fn().mockResolvedValue({ likes: 10, shares: 5 }),
};

Coverage Targets

TypeTarget
Unit80%
Integration60%
E2ECritical paths