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 jsdomUpdate 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