Back to Repositories

Testing Recipe Machine Error Handling in GatsbyJS

This test suite validates error handling in the Gatsby Recipes state machine implementation using XState. It verifies various error scenarios including schema validation, invalid JSX, and missing required parameters.

Test Coverage Overview

The test suite provides comprehensive coverage of error handling scenarios in the recipe-machine module.

Key areas tested include:
  • Schema validation failures
  • Invalid introduction step commands
  • Missing source/recipe path validation
  • Invalid JSX syntax handling
Each test case verifies proper error state transitions and context updates.

Implementation Analysis

The tests utilize XState’s interpret function to simulate the state machine execution flow. The implementation follows an event-driven pattern where state transitions are monitored through onTransition handlers.

Key patterns include:
  • State machine context initialization
  • Transition monitoring with expectations
  • Service lifecycle management (start/stop)
  • Error state verification

Technical Details

Testing tools and setup:
  • Jest as the test runner
  • XState for state machine implementation
  • Custom recipe machine implementation
  • Asynchronous test execution with done() callback

Best Practices Demonstrated

The test suite demonstrates several testing best practices:

  • Isolated test cases with clear scenarios
  • Proper cleanup with service.stop()
  • Async test handling
  • Explicit error condition verification
  • Structured test organization with describe blocks

gatsbyjs/gatsby

deprecated-packages/gatsby-recipes/src/recipe-machine/errors.test.js

            
import { interpret } from "xstate"

import recipeMachine from "."

describe(`recipe-machine errors`, () => {
  it(`errors if part of the recipe fails schema validation`, done => {
    const initialContext = {
      src: `
# Hello, world

---

<File path="./hi.md" contentz="#yo" />
    `,
    }
    const service = interpret(
      recipeMachine.withContext(initialContext)
    ).onTransition(state => {
      if (state.value === `doneError`) {
        expect(
          state.context.plan
            .filter(p => p.error)
            .map(p => p.error)
            .join(``)
        ).toBeTruthy()
        service.stop()
        done()
      } else if (state.value === `presentPlan`) {
        service.send(`CONTINUE`)
      }
    })

    service.start()
  })

  it(`errors if the introduction step has a command`, done => {
    const initialContext = {
      src: `
  # Hello, world

  <File path="./hi.md" content="#yo" />
      `,
    }
    const service = interpret(
      recipeMachine.withContext(initialContext)
    ).onTransition(state => {
      if (state.value === `doneError`) {
        expect(state.context.error).toBeTruthy()
        service.stop()
        done()
      }
    })

    service.start()
  })

  it(`errors if no src or recipePath has been given`, done => {
    const initialContext = {}
    const service = interpret(
      recipeMachine.withContext(initialContext)
    ).onTransition(state => {
      if (state.value === `doneError`) {
        expect(state.context.error).toBeTruthy()
        service.stop()
        done()
      }
    })

    service.start()
  })

  it(`errors if invalid jsx is passed`, done => {
    const initialContext = {
      src: `
# Hello, world

<File path="./hi.md" contentz="#yo" /
    `,
    }
    const service = interpret(
      recipeMachine.withContext(initialContext)
    ).onTransition(state => {
      if (state.value === `doneError`) {
        expect(state.context.error).toBeTruthy()
        service.stop()
        done()
      }
    })

    service.start()
  })
})