Skip to Content

Testing

Because WebF builds upon the standard web technology stack, you can use the same industry-standard tools and methodologies you already know to test your application’s logic and components.

Unit Testing

For testing individual functions, hooks, or business logic in isolation, you can use any standard JavaScript testing framework like Jest or Vitest. These tests run in a Node.js environment and do not require a browser or a WebF instance.

Example with Vitest

import { expect, test } from 'vitest'; function add(a, b) { return a + b; } test('add function works', () => { expect(add(1, 2)).toBe(3); });

Testing Business Logic

import { expect, test, describe } from 'vitest'; import { calculateTotal, applyDiscount } from './utils'; describe('Shopping Cart Utils', () => { test('calculates total correctly', () => { const items = [ { price: 10, quantity: 2 }, { price: 5, quantity: 3 } ]; expect(calculateTotal(items)).toBe(35); }); test('applies discount correctly', () => { expect(applyDiscount(100, 0.1)).toBe(90); }); });

Component Testing

To test your components’ behavior and rendering logic without needing to run the full application, you can use a component testing library in a simulated DOM environment (jsdom).

React Testing Library

For React, the recommended tool is React Testing Library combined with a test runner like Vitest.

// Example using Vitest and React Testing Library import { render, screen, fireEvent } from '@testing-library/react'; import { test, expect } from 'vitest'; import MyComponent from './MyComponent'; test('MyComponent updates on click', () => { render(<MyComponent />); const button = screen.getByRole('button'); fireEvent.click(button); expect(screen.getByText(/count is 1/i)).toBeInTheDocument(); });

Testing User Interactions

import { render, screen, fireEvent } from '@testing-library/react'; import { test, expect } from 'vitest'; import TodoList from './TodoList'; test('adds a new todo item', () => { render(<TodoList />); const input = screen.getByPlaceholderText('Add a todo'); const button = screen.getByText('Add'); fireEvent.change(input, { target: { value: 'New task' } }); fireEvent.click(button); expect(screen.getByText('New task')).toBeInTheDocument(); });

Testing Async Behavior

import { render, screen, waitFor } from '@testing-library/react'; import { test, expect, vi } from 'vitest'; import DataFetcher from './DataFetcher'; test('displays data after fetching', async () => { // Mock the fetch API global.fetch = vi.fn(() => Promise.resolve({ json: () => Promise.resolve({ message: 'Hello' }) }) ); render(<DataFetcher />); // Wait for the data to appear await waitFor(() => { expect(screen.getByText('Hello')).toBeInTheDocument(); }); });

Integration Testing

Integration tests verify that multiple components or modules work together correctly. You can use the same tools as component testing but test larger pieces of your application.

import { render, screen, fireEvent } from '@testing-library/react'; import { test, expect } from 'vitest'; import App from './App'; test('user can complete a full workflow', async () => { render(<App />); // Login fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'user@example.com' } }); fireEvent.change(screen.getByLabelText('Password'), { target: { value: 'password123' } }); fireEvent.click(screen.getByText('Login')); // Navigate and interact await waitFor(() => { expect(screen.getByText('Welcome back!')).toBeInTheDocument(); }); });

End-to-End (E2E) Testing

Note: True End-to-End (E2E) testing, which involves programmatically controlling the final, running application in the native WebF shell, is not currently supported. The recommended approach is to focus on thorough unit and component testing.

Testing Best Practices

1. Follow the Testing Trophy

Focus your testing effort according to the testing trophy:

  • Many unit tests: Fast, cheap, and test business logic in isolation
  • Some integration tests: Test how components work together
  • Few E2E tests: Currently not supported, but would test the entire application

2. Test User Behavior, Not Implementation

// ❌ Bad - testing implementation details test('state updates correctly', () => { const { result } = renderHook(() => useCounter()); expect(result.current.count).toBe(0); }); // ✅ Good - testing user-visible behavior test('displays count and increments on button click', () => { render(<Counter />); expect(screen.getByText('Count: 0')).toBeInTheDocument(); fireEvent.click(screen.getByText('Increment')); expect(screen.getByText('Count: 1')).toBeInTheDocument(); });

3. Use Test IDs Sparingly

Prefer querying by role, label, or text over test IDs:

// ✅ Good - accessible and semantic const button = screen.getByRole('button', { name: 'Submit' }); // ⚠️ Okay for complex cases const element = screen.getByTestId('complex-component'); // ❌ Avoid if possible const div = screen.getByClassName('some-class');

4. Mock External Dependencies

import { vi } from 'vitest'; // Mock a module vi.mock('./api', () => ({ fetchUser: vi.fn(() => Promise.resolve({ name: 'John' })) })); // Mock a function const mockCallback = vi.fn();

5. Clean Up After Tests

import { afterEach } from 'vitest'; import { cleanup } from '@testing-library/react'; afterEach(() => { cleanup(); vi.clearAllMocks(); });

Setting Up Vitest

Add Vitest to your project:

npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom

Update your vite.config.js:

import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', setupFiles: './src/test/setup.js', }, });

Create src/test/setup.js:

import '@testing-library/jest-dom';

Add test script to package.json:

{ "scripts": { "test": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage" } }

Running Tests

# Run tests in watch mode npm test # Run tests once npm test -- --run # Run tests with UI npm run test:ui # Generate coverage report npm run test:coverage