Extending
Adding Testing Presets

Adding a New Testing Preset

Comprehensive guide to adding new testing frameworks and configurations (e.g., Cypress, Playwright, Testing Library variants, custom Jest configurations) to React Kickstart's testing options.

Current Options: React Kickstart currently supports Vitest and Jest. This guide shows how to add additional testing frameworks and presets.


๐ŸŽฏ Integration Overview


๐Ÿ“‹ Complete Integration Guide

Declare Testing Dependencies

File: src/builders/dependencies.js

Add your testing framework dependencies:

// Cypress E2E testing dependencies
export const testing = {
  // ...existing testing dependencies
  cypress: "^13.6.0",
  cypressRealEvents: "^1.11.0",
  cypressAxeCore: "^4.8.0" // Accessibility testing
}
 
export function getCypressDependencies(includePlugins = true) {
  const deps = {
    cypress: testing.cypress
  }
  
  if (includePlugins) {
    deps["cypress-real-events"] = testing.cypressRealEvents
    deps["cypress-axe"] = testing.cypressAxeCore
  }
  
  return deps
}

Create Testing Setup Module

File: src/features/testing/your-testing-setup.js

Create comprehensive setup modules:

// src/features/testing/cypress-setup.js
import { CORE_UTILS } from '../../utils/index.js'
import path from 'path'
 
export class CypressSetup {
  async setup(projectPath, userChoices) {
    await this.createCypressConfig(projectPath, userChoices)
    await this.createCypressSupport(projectPath, userChoices)
    await this.createExampleTests(projectPath, userChoices)
    await this.createCommands(projectPath, userChoices)
    await this.updatePackageScripts(projectPath, userChoices)
  }
  
  async createCypressConfig(projectPath, userChoices) {
    const { framework, typescript } = userChoices
    
    const configContent = `
import { defineConfig } from 'cypress'
 
export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:${framework === 'vite' ? '5173' : '3000'}',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: false,
    screenshotOnRunFailure: true,
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    supportFile: 'cypress/support/e2e.${typescript ? 'ts' : 'js'}'
  },
  component: {
    devServer: {
      framework: '${framework}',
      bundler: '${framework === 'vite' ? 'vite' : 'webpack'}',
    },
    specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
    supportFile: 'cypress/support/component.${typescript ? 'ts' : 'js'}'
  }
})
    `
    
    const configPath = path.join(projectPath, `cypress.config.${typescript ? 'ts' : 'js'}`)
    await CORE_UTILS.writeFile(configPath, configContent.trim())
  }
  
  async createCypressSupport(projectPath, userChoices) {
    const { typescript } = userChoices
    
    // E2E Support
    const e2eSupportContent = `
import './commands'
import 'cypress-axe'
 
// Hide fetch/XHR requests from command log
Cypress.on('window:before:load', (win) => {
  cy.stub(win.console, 'error').callsFake((message) => {
    // Filter out specific errors you want to ignore
    if (!message.includes('ResizeObserver loop limit exceeded')) {
      Cypress.log({
        name: 'console.error',
        message: message
      })
    }
  })
})
 
// Global before hook
beforeEach(() => {
  // Inject axe-core for accessibility testing
  cy.injectAxe()
})
    `
    
    const e2eSupportPath = path.join(projectPath, 'cypress', 'support', `e2e.${typescript ? 'ts' : 'js'}`)
    await CORE_UTILS.ensureDirectory(path.dirname(e2eSupportPath))
    await CORE_UTILS.writeFile(e2eSupportPath, e2eSupportContent.trim())
    
    // Component Support
    const componentSupportContent = `
import './commands'
import { mount } from 'cypress/react18'
 
// Augment the Cypress namespace to include type definitions for custom commands
declare global {
  namespace Cypress {
    interface Chainable {
      mount: typeof mount
    }
  }
}
 
Cypress.Commands.add('mount', mount)
    `
    
    const componentSupportPath = path.join(projectPath, 'cypress', 'support', `component.${typescript ? 'ts' : 'js'}`)
    await CORE_UTILS.writeFile(componentSupportPath, componentSupportContent.trim())
  }
  
  async createCommands(projectPath, userChoices) {
    const { typescript } = userChoices
    
    const commandsContent = `
// Custom Cypress commands
 
// Login command
Cypress.Commands.add('login', (email = 'test@example.com', password = 'password') => {
  cy.visit('/login')
  cy.get('[data-testid="email-input"]').type(email)
  cy.get('[data-testid="password-input"]').type(password)
  cy.get('[data-testid="login-button"]').click()
  cy.url().should('not.include', '/login')
})
 
// Custom assertion for accessibility
Cypress.Commands.add('checkA11y', (context = null, options = {}) => {
  cy.checkA11y(context, {
    includedImpacts: ['critical', 'serious'],
    ...options
  })
})
 
// Wait for React to be ready
Cypress.Commands.add('waitForReact', () => {
  cy.window().should('have.property', 'React')
})
 
// Custom command for API mocking
Cypress.Commands.add('mockApi', (method, url, response, statusCode = 200) => {
  cy.intercept(method, url, {
    statusCode,
    body: response
  }).as('apiCall')
})
 
${typescript ? `
// TypeScript declarations
declare global {
  namespace Cypress {
    interface Chainable {
      login(email?: string, password?: string): Chainable<void>
      checkA11y(context?: any, options?: any): Chainable<void>
      waitForReact(): Chainable<void>
      mockApi(method: string, url: string, response: any, statusCode?: number): Chainable<void>
    }
  }
}
` : ''}
    `
    
    const commandsPath = path.join(projectPath, 'cypress', 'support', `commands.${typescript ? 'ts' : 'js'}`)
    await CORE_UTILS.writeFile(commandsPath, commandsContent.trim())
  }
  
  async createExampleTests(projectPath, userChoices) {
    const { typescript } = userChoices
    
    // E2E Test
    const e2eTestContent = `
describe('App E2E Tests', () => {
  beforeEach(() => {
    cy.visit('/')
  })
  
  it('should display the welcome message', () => {
    cy.contains('Welcome to React Kickstart!')
    cy.get('h1').should('be.visible')
  })
  
  it('should be accessible', () => {
    cy.checkA11y()
  })
  
  it('should navigate correctly', () => {
    // Add navigation tests based on your app structure
    cy.get('[data-testid="nav-link"]').first().click()
    cy.url().should('include', '/')
  })
  
  it('should handle responsive design', () => {
    // Test mobile viewport
    cy.viewport('iphone-x')
    cy.get('h1').should('be.visible')
    
    // Test desktop viewport
    cy.viewport(1280, 720)
    cy.get('h1').should('be.visible')
  })
})
    `
    
    const e2eTestPath = path.join(projectPath, 'cypress', 'e2e', `app.cy.${typescript ? 'ts' : 'js'}`)
    await CORE_UTILS.ensureDirectory(path.dirname(e2eTestPath))
    await CORE_UTILS.writeFile(e2eTestPath, e2eTestContent.trim())
    
    // Component Test
    const componentTestContent = `
import React from 'react'
import App from '../../src/App'
 
describe('App Component Tests', () => {
  it('should render the app component', () => {
    cy.mount(<App />)
    cy.contains('Welcome to React Kickstart!')
  })
  
  it('should handle user interactions', () => {
    cy.mount(<App />)
    // Add interaction tests based on your components
  })
})
    `
    
    const componentTestPath = path.join(projectPath, 'src', 'components', `App.cy.${typescript ? 'tsx' : 'jsx'}`)
    await CORE_UTILS.ensureDirectory(path.dirname(componentTestPath))
    await CORE_UTILS.writeFile(componentTestPath, componentTestContent.trim())
  }
  
  async updatePackageScripts(projectPath, userChoices) {
    const packageJsonPath = path.join(projectPath, 'package.json')
    const packageJson = JSON.parse(await CORE_UTILS.readFile(packageJsonPath))
    
    packageJson.scripts = {
      ...packageJson.scripts,
      'cypress:open': 'cypress open',
      'cypress:run': 'cypress run',
      'cypress:run:headless': 'cypress run --headless',
      'test:e2e': 'cypress run',
      'test:e2e:dev': 'cypress open'
    }
    
    await CORE_UTILS.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
  }
}

Update Prompt System

File: src/prompts/steps/testing-step.js

Add your testing options:

getChoices(previousAnswers) {
  const { framework } = previousAnswers
 
  return [
    {
      name: 'Vitest (Fast, modern testing framework)',
      value: 'vitest',
      short: 'Vitest'
    },
    {
      name: 'Jest (Traditional, mature testing framework)',
      value: 'jest',
      short: 'Jest'
    },
    {
      name: 'Cypress (E2E and component testing)',
      value: 'cypress',
      short: 'Cypress'
    },
    {
      name: 'Playwright (Modern E2E testing)',
      value: 'playwright',
      short: 'Playwright'
    },
    {
      name: 'Enhanced RTL (React Testing Library with MSW)',
      value: 'enhanced-rtl',
      short: 'Enhanced RTL'
    },
    {
      name: 'None (No testing setup)',
      value: 'none',
      short: 'None'
    }
  ].map(choice => {
    // Add framework-specific recommendations
    if (choice.value === 'vitest' && framework === 'vite') {
      choice.name += ' (Recommended for Vite)'
    }
    if (choice.value === 'jest' && framework === 'nextjs') {
      choice.name += ' (Recommended for Next.js)'
    }
    return choice
  })
}

Integrate with Feature System

File: src/features/testing/index.js

Update the main testing integration:

import { VitestSetup } from "./vitest-setup.js";
import { JestSetup } from "./jest-setup.js";
import { CypressSetup } from "./cypress-setup.js";
import { PlaywrightSetup } from "./playwright-setup.js";
import { EnhancedRTLSetup } from "./enhanced-rtl-setup.js";
 
export async function setupTesting(projectPath, userChoices) {
  const { testing } = userChoices;
 
  switch (testing) {
    case "vitest":
      const vitestSetup = new VitestSetup();
      await vitestSetup.setup(projectPath, userChoices);
      break;
 
    case "jest":
      const jestSetup = new JestSetup();
      await jestSetup.setup(projectPath, userChoices);
      break;
 
    case "cypress":
      const cypressSetup = new CypressSetup();
      await cypressSetup.setup(projectPath, userChoices);
      break;
 
    case "playwright":
      const playwrightSetup = new PlaywrightSetup();
      await playwrightSetup.setup(projectPath, userChoices);
      break;
 
    case "enhanced-rtl":
      const enhancedRTLSetup = new EnhancedRTLSetup();
      await enhancedRTLSetup.setup(projectPath, userChoices);
      break;
 
    case "none":
    default:
      // No testing setup
      break;
  }
}

Add to QA System

File: qa-automation/test-matrix-generator.js

Add testing options to the test matrix:

const CONFIG_OPTIONS = {
  framework: ["vite", "nextjs"],
  typescript: [true, false],
  testing: ["vitest", "jest", "cypress", "playwright", "enhanced-rtl", "none"],
  // ... other options
};
 
// Add testing-specific validation
const TESTING_VALIDATION = {
  cypress: {
    requiredFiles: ["cypress.config.ts", "cypress/support/e2e.ts"],
    requiredDependencies: ["cypress"],
  },
  playwright: {
    requiredFiles: ["playwright.config.ts", "tests/app.spec.ts"],
    requiredDependencies: ["@playwright/test"],
  },
  "enhanced-rtl": {
    requiredFiles: ["src/test/setup.ts", "src/test/utils.tsx"],
    requiredDependencies: ["@testing-library/react", "msw"],
  },
};

๐Ÿงช Testing Your Integration

Manual Testing

Test your testing preset:

# Test Cypress integration
node bin/react-kickstart.js test-cypress --yes --framework vite --testing cypress --typescript
 
# Test Playwright integration
node bin/react-kickstart.js test-playwright --yes --framework nextjs --testing playwright
 
# Test Enhanced RTL
node bin/react-kickstart.js test-rtl --yes --framework vite --testing enhanced-rtl --state redux

Verify Testing Works

cd test-cypress
npm install
npm run test  # Should run tests successfully
npm run cypress:open  # Should open Cypress

๐Ÿ“š Best Practices

Testing Strategy: Provide comprehensive testing setups that cover unit, integration, and E2E testing needs.

  • Test Organization - Structure tests logically with clear naming conventions
  • Utilities - Provide helpful testing utilities and custom matchers
  • Mocking - Include proper mocking strategies for APIs and external dependencies
  • Accessibility - Integrate accessibility testing where appropriate
  • Performance - Consider test execution performance and parallelization

๐Ÿ“š Next Steps

Related Guides:

Testing Ecosystem: Your testing integration expands React Kickstart's quality assurance capabilities and helps developers build more reliable applications.