Frontend - Development

Frontend Testing

Tools and Setup

Test Framework

  • Vitest for unit and integration tests
  • React Testing Library for component testing
  • Storybook for component development and visual testing
  • MSW (Mock Service Worker) for API mocking

Component Testing

Button Component Example

import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@/components/ui/button';

describe('Button', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });

  it('handles click events', () => {
    const handleClick = vi.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    fireEvent.click(screen.getByText('Click me'));
    expect(handleClick).toHaveBeenCalled();
  });
});

Form Testing

describe('CreateProjectForm', () => {
  const mockOnClose = vi.fn();

  beforeEach(() => {
    render(<CreateProjectForm onClose={mockOnClose} />);
  });

  it('validates required fields', async () => {
    const submitButton = screen.getByText('Next');
    fireEvent.click(submitButton);
    
    expect(await screen.findByText('Authorization is required')).toBeInTheDocument();
    expect(await screen.findByText('Repository is required')).toBeInTheDocument();
  });

  it('handles multi-step form navigation', async () => {
    // Fill first step
    await selectAuthorization('github');
    await selectRepository('user/repo');
    
    const nextButton = screen.getByText('Next');
    fireEvent.click(nextButton);

    // Verify second step is shown
    expect(screen.getByText('Resource Configuration')).toBeInTheDocument();
  });
});

Redux Testing

Store Setup

const createTestStore = (initialState = {}) => {
  return configureStore({
    reducer: {
      auth: authReducer,
      config: configReducer,
    },
    preloadedState: initialState,
  });
};

const renderWithStore = (
  ui: React.ReactElement,
  initialState = {}
) => {
  const store = createTestStore(initialState);
  return {
    ...render(<Provider store={store}>{ui}</Provider>),
    store,
  };
};

Redux Hook Testing

describe('useStore', () => {
  it('provides access to auth state', () => {
    const { result } = renderHook(() => useStore(), {
      wrapper: createWrapper({
        auth: { isAuth: true, token: 'test-token' }
      })
    });

    expect(result.current.state.auth.isAuth).toBe(true);
    expect(result.current.state.auth.token).toBe('test-token');
  });
});

API Mocking

MSW Setup

import { setupServer } from 'msw/node';
import { rest } from 'msw';

const server = setupServer(
  rest.get('/api/projects', (req, res, ctx) => {
    return res(ctx.json([
      { id: '1', name: 'Test Project' }
    ]));
  }),
  
  rest.post('/api/projects', (req, res, ctx) => {
    return res(ctx.json({ id: '2', ...req.body }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Testing API Interactions

describe('ProjectsList', () => {
  it('displays projects from API', async () => {
    render(<ProjectsList />);
    
    // Wait for data to load
    await screen.findByText('Test Project');
    
    // Verify project card is displayed
    expect(screen.getByText('Test Project')).toBeInTheDocument();
  });

  it('handles API errors', async () => {
    server.use(
      rest.get('/api/projects', (req, res, ctx) => {
        return res(ctx.status(500));
      })
    );

    render(<ProjectsList />);
    
    // Verify error message is displayed
    expect(await screen.findByText('Error loading projects')).toBeInTheDocument();
  });
});

Storybook Tests

Component Stories

import type { Meta, StoryObj } from '@storybook/react';
import { ProjectCard } from './ProjectCard';

const meta: Meta<typeof ProjectCard> = {
  component: ProjectCard,
  args: {
    project: {
      id: '1',
      name: 'Test Project',
      status: 'running',
    },
  },
};

export default meta;
type Story = StoryObj<typeof ProjectCard>;

export const Default: Story = {};

export const Loading: Story = {
  args: {
    project: {
      ...meta.args.project,
      status: 'loading',
    },
  },
};

Visual Regression Testing

describe('ProjectCard', () => {
  it('visual regression test', async () => {
    const canvas = within(canvasElement);
    
    // Take snapshot for comparison
    await expect(canvas).toMatchImageSnapshot();
  });
});

Integration Testing

Router Testing

describe('App Routing', () => {
  it('redirects unauthenticated users', () => {
    render(
      <MemoryRouter initialEntries={['/dashboard']}>
        <App />
      </MemoryRouter>
    );

    expect(screen.getByText('Login')).toBeInTheDocument();
  });

  it('allows authenticated access', () => {
    const { store } = renderWithStore(
      <MemoryRouter initialEntries={['/dashboard']}>
        <App />
      </MemoryRouter>,
      {
        auth: { isAuth: true, token: 'valid-token' }
      }
    );

    expect(screen.getByText('Projects')).toBeInTheDocument();
  });
});

Test Best Practices

  1. Component Testing

    • Test user interactions
    • Verify state changes
    • Check error handling
    • Test accessibility
  2. Integration Testing

    • Test component combinations
    • Verify data flow
    • Check routing behavior
    • Test real-world scenarios
  3. Visual Testing

    • Capture component variations
    • Test responsive behavior
    • Check theme changes
    • Verify animations
Previous
State