Using AI to Write Your First Tests

Jest Tests running terminal image

Introduction

As a developer primarily working with PHP, TypeScript, and Laravel, diving into the world of JavaScript testing might seem daunting. However, when faced with package manager issues while building Syndeos—a desktop application for VPS management—I discovered that writing tests wasn't just about catching bugs; it was about ensuring package compatibility and creating a more robust development workflow.

This article chronicles my journey of using AI assistance to write my first tests, specifically focused on catching package incompatibility issues that arose when switching from npm to pnpm.

The Problem: Package Manager Compatibility

While developing Syndeos with Tauri v2, React 19, and various modern libraries, I encountered a frustrating issue. When attempting to switch from npm to pnpm for better performance and disk space efficiency, I discovered that npm's stricter dependency resolution had been masking compatibility issues between packages.

The specific issues were:

  • react-day-picker version conflicts with date-fns
  • Certain peer dependency mismatches that pnpm's more lenient hoisting exposed

Rather than staying locked into npm or manually checking compatibility for every update, I decided to write automated tests—something I'd never done before in the JavaScript ecosystem.

Why Tests for Package Compatibility?

Traditional testing focuses on functionality, but package compatibility tests serve a different purpose:

  1. Early Detection: Catch incompatibility issues before they break the build
  2. Package Manager Agnostic: Ensure the codebase works regardless of the package manager
  3. Documentation: Tests serve as living documentation of package requirements
  4. CI/CD Integration: Automated checks in the build pipeline prevent breaking changes

Writing My First Compatibility Test with AI Assistance

Step 1: Understanding the Test Structure

Coming from a Laravel background where testing follows clear MVC patterns, I needed to understand JavaScript testing conventions. With AI assistance, I learned that compatibility tests should:

  • Import the problematic packages together
  • Attempt to use them in ways that would expose incompatibility
  • Verify that the expected APIs are available and functional

Step 2: The First Test File

Here's the test I wrote with AI guidance for checking react-day-picker and date-fns compatibility:

import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { DayPicker } from 'react-day-picker';
import * as dateFns from 'date-fns';

describe('react-day-picker and date-fns compatibility', () => {
  it('DayPicker renders without crashing', () => {
    render(<DayPicker />);
  });

  it('DayPicker formats dates correctly with date-fns', () => {
    const today = new Date();

    render(
      <DayPicker
        month={today}
        formatters={{
          formatMonthCaption: (date) => dateFns.format(date, 'MMMM yyyy'),
          formatWeekdayName: (date) => dateFns.format(date, 'EEE'),
        }}
      />
    );

    const monthCaption = dateFns.format(today, 'MMMM yyyy');
    expect(screen.getByText(monthCaption)).toBeInTheDocument();
  });

  it('renders with correct month and allows interactions', async () => {
    const onSelectSpy = vi.fn();
    const today = new Date();
    const currentMonth = dateFns.format(today, 'MMMM yyyy');

    const { container } = render(
      <DayPicker
        mode="single"
        selected={today}
        onSelect={onSelectSpy}
        defaultMonth={today}
      />
    );

    expect(screen.getByText(currentMonth)).toBeInTheDocument();

    const dayCells = container.querySelectorAll('.rdp-day');
    expect(dayCells.length).toBeGreaterThan(0);

    const dayToClick = Array.from(dayCells).find(cell =>
      cell.getAttribute('aria-disabled') !== 'true' &&
      !cell.classList.contains('rdp-day_outside')
    );

    if (dayToClick) {
      await userEvent.click(dayToClick);
      expect(onSelectSpy).toHaveBeenCalled();
    }
  });

  it('DayPicker uses complex date-fns features correctly', () => {
    const startDate = dateFns.startOfWeek(new Date());
    const endDate = dateFns.endOfWeek(new Date());

    render(
      <DayPicker
        mode="range"
        defaultMonth={startDate}
        selected={{
          from: startDate,
          to: endDate
        }}
      />
    );
  });
});

Step 3: Integrating Tests into the Build Process

The real power comes from integrating these tests into the build pipeline. I modified the package.json to run tests before building:

{
  "scripts": {
    "build": "tsc && vitest run && vite build",
    "test": "vitest run",
    "test:watch": "vitest"
  }
}

This ensures that:

  1. TypeScript compilation happens first (tsc)
  2. Tests run to verify compatibility (vitest run)
  3. Only if tests pass does the build continue (vite build)

Key Learnings and Best Practices

1. Start Simple

My first test simply verified that components could render. This caught the most basic incompatibility issues.

2. Test Real-World Usage

Don't just import packages—use them together in ways that mirror your actual application code.

3. Use AI as a Learning Tool

AI helped me understand:

  • Testing library conventions
  • How to structure compatibility tests
  • Best practices for async testing
  • The difference between unit and integration tests

4. Focus on Integration Points

The most valuable tests checked where packages interact:

  • Date formatting with external libraries
  • Event handling across package boundaries
  • Shared type definitions

5. Document Test Purpose

Each test includes a clear description of what compatibility issue it's checking for.

Benefits Beyond Compatibility

Writing these tests provided unexpected benefits:

  1. Confidence in Updates: I can now update packages knowing tests will catch breaking changes
  2. Better Understanding: Writing tests forced me to understand how packages actually work together
  3. Living Documentation: Tests serve as examples of proper package usage
  4. Team Onboarding: New developers can understand package relationships through tests

Applying MVC Thinking to JavaScript Testing

Coming from Laravel's MVC architecture, I found it helpful to think of tests in similar terms:

  • Model Tests: Verify data structures and transformations work across packages
  • View Tests: Ensure UI components from different packages render together
  • Controller Tests: Check that event handlers and state management integrate properly

Practical Tips for First-Time Test Writers

  1. Use AI to Bootstrap: Don't be afraid to ask AI for test structure examples
  2. Start with Happy Paths: Test the normal use cases before edge cases
  3. Run Tests Frequently: Use test:watch during development
  4. Test One Thing: Each test should verify a single compatibility aspect
  5. Mock Sparingly: For compatibility tests, use real implementations when possible

Conclusion

Writing my first JavaScript tests to solve package compatibility issues taught me that testing isn't just about catching bugs—it's about ensuring the entire ecosystem of packages works harmoniously together. By integrating these tests into the build process, I've created a safety net that allows me to use pnpm's benefits while ensuring compatibility that npm's strict resolution previously enforced.

The journey from encountering package compatibility errors to having automated tests that prevent such issues has been transformative. It's shown me that with the right approach and tools (including AI assistance), even developers new to testing can create valuable test suites that solve real problems.

For fellow developers facing similar package manager transitions or compatibility concerns, I encourage you to write that first test. Start simple, use AI as a learning companion, and focus on the specific problems you're trying to solve. The investment in learning testing will pay dividends in confidence, code quality, and development speed.

Remember: every expert was once a beginner. Your first test doesn't need to be perfect—it just needs to solve a real problem. In my case, that problem was package compatibility, and the solution has made Syndeos a more robust and maintainable project.