You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ghost/apps/posts/test/utils/MSW_USAGE_GUIDE.md

5.0 KiB

MSW Testing Guide - Simplified

Clean, composable mock server setup with just 2 patterns.

Quick Start

import {mockServer, mockData} from '../utils/msw-helpers';

// 90% of cases - just declare what data you want
mockServer.setup({
    posts: [mockData.post({title: 'My Post'})],
    feedback: [{id: '1', score: 1, message: 'Great!'}]
});

The Two Patterns

Pattern 1: Declarative Data (90% of cases)

Just arrays of data for Ghost API endpoints:

mockServer.setup({
    posts: [
        mockData.post({title: 'Post 1'}),
        mockData.post({title: 'Post 2'})
    ],
    feedback: [
        {id: '1', score: 1, message: 'Great!'},
        {id: '2', score: 0, message: 'Needs work'}
    ],
    links: [],  // Empty array = no results
    newsletterBasicStats: [{post_id: 'post-1', open_rate: 0.3}]
});

Available endpoints:

  • posts/ghost/api/admin/posts/*
  • feedback/ghost/api/admin/feedback/*
  • links/ghost/api/admin/links/
  • newsletterBasicStats/ghost/api/admin/stats/newsletter-basic-stats/
  • newsletterClickStats/ghost/api/admin/stats/newsletter-click-stats/

Pattern 2: Custom Handlers (10% of cases)

For everything complex - errors, external APIs, conditional logic:

import {endpoint, when} from '../utils/msw-helpers';

mockServer.setup({
    // Standard data
    posts: [mockData.post()],
    
    // Complex scenarios go in customHandlers
    customHandlers: [
        // External APIs
        endpoint.get('/api/external', {data: 'test'}),
        endpoint.post('/api/webhook', {success: true}, 201),
        
        // Error scenarios  
        endpoint.get('/ghost/api/admin/posts/*', {error: 'Server error'}, 500),
        
        // Conditional responses
        when('get', '/ghost/api/admin/feedback/*', [
            {if: req => req.url.includes('score=1'), response: {feedback: positiveFeedback}},
            {if: req => req.url.includes('score=0'), response: {feedback: negativeFeedback}}
        ], {feedback: []})
    ]
});

Practical Examples

Basic Hook Testing

test('loads post data', () => {
    mockServer.setup({
        posts: [mockData.post({id: 'test-id', title: 'Test Post'})]
    });
    
    const {result} = renderHook(() => usePost('test-id'), {
        wrapper: createTestWrapper()
    });
    
    expect(result.current.data?.title).toBe('Test Post');
});

Error Scenarios

test('handles server errors', () => {
    mockServer.setup({
        customHandlers: [
            endpoint.get('/ghost/api/admin/posts/*', {error: 'Server error'}, 500)
        ]
    });
    
    // Test error handling...
});

Complex Component Testing

test('PostAnalytics with full data', () => {
    mockServer.setup({
        posts: [mockData.post({
            id: 'post-1',
            count: {clicks: 100, positive_feedback: 5, negative_feedback: 2}
        })],
        feedback: [
            {id: '1', score: 1, message: 'Great!'},
            {id: '2', score: 0, message: 'Needs work'}
        ],
        links: [
            {id: 'link-1', clicks: 50, link: {title: 'Link', to: '/page'}}
        ]
    });
    
    render(<PostAnalytics postId="post-1" />, {wrapper: createTestWrapper()});
    // Assertions...
});

Reusable Test Scenarios

// Store scenarios as plain objects
const successScenario = {
    posts: [mockData.post({title: 'Success Post'})],
    feedback: [{id: '1', score: 1, message: 'Good!'}]
};

const errorScenario = {
    customHandlers: [
        endpoint.get('/ghost/api/admin/posts/*', {error: 'Failed'}, 500)
    ]
};

// Reuse across tests
test('success case', () => {
    mockServer.setup(successScenario);
    // Test...
});

test('error case', () => {
    mockServer.setup(errorScenario);
    // Test...
});

// Compose scenarios
test('mixed case', () => {
    mockServer.setup({
        ...successScenario,
        customHandlers: [
            endpoint.get('/api/external', {data: 'external'})
        ]
    });
    // Test...
});

Built-in Defaults

These are automatically included in every test:

  • site - Basic site info
  • config - App configuration
  • settings - Ghost settings
  • tinybirdToken - Analytics token

Override if needed:

mockServer.setup({
    site: {url: 'https://custom.com', title: 'Custom Site'},
    config: {stats: {enabled: false}}
});

Mock Data Factories

Use mockData for consistent test data:

// With defaults
mockData.post() // → {id: 'test-post-id', title: 'Test Post', ...}

// With overrides  
mockData.post({title: 'Custom Title', count: {clicks: 999}})

// Direct arrays (when you need simple data)
const posts = [
    {id: '1', title: 'Post 1'},
    {id: '2', title: 'Post 2'}
];

Quick Helper

For PostAnalyticsProvider tests:

import {setupPostAnalyticsProvider} from '../utils/msw-helpers';

test('with analytics provider', () => {
    setupPostAnalyticsProvider('my-post-id');
    // Provider will have post data available
});