Skip to content

Latest commit

 

History

History
283 lines (251 loc) · 7.79 KB

File metadata and controls

283 lines (251 loc) · 7.79 KB

Unittest Guidelines

General Structure

Test File Organization

  • Place test files in __test__ directories
  • Name test files with .test.ts suffix
  • Group related tests in describe blocks
  • Use clear, descriptive test names
  • All Graphql api tests are placed under src/types/__test__/
  • Example: src/types/__test__/2/channel/articles.test.ts (related compiled file build/types/__test__/2/channel/articles.test.js)

Setup and Teardown

let connections: Connections
let channelService: ChannelService
let atomService: AtomService

beforeAll(async () => {
  connections = await genConnections()
  channelService = new ChannelService(connections)
  atomService = new AtomService(connections)
}, 30000) // Set appropriate timeout

afterAll(async () => {
  await closeConnections(connections)
})

beforeEach(async () => {
  // Clean up previous test data
  await atomService.deleteMany({ table: 'topic_channel_article' })
  await atomService.deleteMany({ table: 'topic_channel' })
})

Testing Patterns

GraphQL Tests

  1. Query Structure

    const GET_CHANNEL_ARTICLES = /* GraphQL */ `
      query GetChannelArticles(
        $channelInput: ChannelInput!
        $articleInput: ChannelArticlesInput!
      ) {
        channel(input: $channelInput) {
          ... on TopicChannel {
            articles(input: $articleInput) {
              edges {
                pinned
                node {
                  id
                }
              }
            }
          }
        }
      }
    `
  2. Test Client Setup

    const server = await testClient({
      connections,
      isAuth: true,
      isAdmin: true, // Use this instead of context: { viewer: admin } if admin.id is not needed
    })
  3. Operation Execution

    const { data, errors } = await server.executeOperation({
      query: GET_CHANNEL_ARTICLES,
      variables: {
        channelInput: {
          shortHash: channel.shortHash, // Required field
        },
        articleInput: {
          first: 10,
          filter: {
            dateTimeRange: { start, end },
          },
        },
      },
    })
  4. Testing Multi-language Content

    const PUT_ANNOUNCEMENT = /* GraphQL */ `
      mutation PutAnnouncement($input: PutAnnouncementInput!) {
        putAnnouncement(input: $input) {
          id
          title
          titleEn: title(input: { language: en })
          content
          cover
          link
          type
          visible
          order
        }
      }
    `
    
    const { data, errors } = await server.executeOperation({
      query: PUT_ANNOUNCEMENT,
      variables: {
        input: {
          title: [
            { language: 'zh_hant', text: '測試標題' },
            { language: 'en', text: 'Test Title' },
          ],
          content: [
            { language: 'zh_hant', text: '測試內容' },
            { language: 'en', text: 'Test Content' },
          ],
          link: [
            { language: 'zh_hant', text: 'https://example.com' },
            { language: 'en', text: 'https://example.com' },
          ],
          type: 'community',
          visible: true,
          order: 1,
        },
      },
    })
    
    expect(errors).toBeUndefined()
    expect(data?.putAnnouncement).toBeDefined()
    expect(data?.putAnnouncement.title).toBe('測試標題')
    expect(data?.putAnnouncement.titleEn).toBe('Test Title')

Data Setup

  1. Test Data Creation

    // Create test channel, prefer using specific Services over AtomService
    const channel = await channelService.createTopicChannel({
      name: 'test-topic',
      providerId: 'test-provider-id',
      enabled: true,
    })
  2. Cleanup

    beforeEach(async () => {
      await atomService.deleteMany({ table: 'topic_channel_article' })
      await atomService.deleteMany({ table: 'topic_channel' })
    })

Assertions

  1. Basic Assertions

    expect(errors).toBeUndefined()
    expect(data?.node.articles.edges).toHaveLength(3)
  2. ID Assertions

    // When asserting IDs, use toGlobalId to match the GraphQL global ID format
    expect(data?.putAnnouncement.channels[0].channel.id).toBe(toGlobalId({
      type: NODE_TYPES.TopicChannel,
      id: channel.id,
    }))

Best Practices

Test Isolation

  • Each test should be independent
  • Clean up data between tests using beforeEach
  • Don't rely on test execution order
  • Example:
    beforeEach(async () => {
      await atomService.deleteMany({ table: 'topic_channel_article' })
      await atomService.deleteMany({ table: 'topic_channel' })
    })

Error Handling

  • Test both success and error cases
  • Verify error codes using errors?.[0].extensions.code, do not verify error messages
  • Handle async errors appropriately
  • Example:
    // Success case
    expect(errors).toBeUndefined()
    expect(data).toBeDefined()
    
    // Error cases
    expect(errors).toBeDefined()
    expect(errors?.[0].extensions.code).toBe('BAD_USER_INPUT')  // For validation errors
    expect(errors?.[0].extensions.code).toBe('ENTITY_NOT_FOUND')  // For not found errors
    expect(errors?.[0].extensions.code).toBe('FORBIDDEN')  // For permission errors

Performance

  • Use appropriate timeouts (e.g., 30000ms for beforeAll)
  • Clean up resources promptly in afterAll
  • Avoid unnecessary setup/teardown

Code Organization

  • Group related tests in describe blocks
  • Use descriptive test names
  • Keep test files focused and concise
  • Example:
    describe('datetimeRange filtering', () => {
      test('filters articles within date range', async () => {
        // Test implementation
      })
    })

UUID Handling

  • Always use v4() from the uuid package to generate unique identifiers
  • Never use hardcoded test UUIDs like 'test-uuid-1' as they can cause conflicts
  • Import v4 at the top of test files: import { v4 } from 'uuid'
  • This ensures test isolation and prevents data conflicts between test runs

Using Seed Data

Available Seed Data

The test database is seeded with predefined data in db/seeds/ directory:

  • 01_users.js: Test users with different roles and states
    • Users: test1-test4, admin1, matty, active, frozen, etc.
    • Various states: active, banned
    • Different roles: admin, user
  • 05_articles.js: Test articles with different content and authors
    • Articles: test article 1-6
    • Different authors and states
  • Other seed files for comments, tags, etc.

Best Practices for Using Seed Data

  1. Prefer Seed Data Over New Data

    // Good: Use existing seed data
    const article = await atomService.findUnique({
      table: 'article',
      where: { id: '1' }, // References test article 1 from seeds
    })
    
    // Avoid: Creating new test data unless necessary
    const newArticle = await articleService.createArticle({
      authorId: '1',
      title: 'test',
      content: 'test',
    })
  2. Document Seed Data Usage

    describe('article search', () => {
      test('finds article by title', async () => {
        // Using seed article "test article 1" from db/seeds/05_articles.js
        const { data } = await server.executeOperation({
          query: SEARCH_ARTICLES,
          variables: {
            input: { searchKey: 'test article 1' },
          },
        })
        expect(data.articles.edges.length).toBeGreaterThan(0)
      })
    })

    Commend to Test A Single Test File

    npm run build && MATTERS_ENV=test node --experimental-vm-modules --no-experimental-fetch node_modules/.bin/jest build/types/__test__/2/channel/articles.test.js

Tips

  • Keep tests focused on one aspect
  • Always check GraphQL schema for required fields in inputs
  • Verify input types match schema definitions
  • Use service methods for data creation when available
  • Prefer using existing user IDs when possible