Testing

Vuesion tests behavior rather than implementation details.

Tests should provide confidence that the application works as users and external clients experience it. They should not become coupled to private component state, internal method calls, or incidental implementation choices.

Vuesion combines different testing techniques to verify behavior at every layer of the application.

Text
 Utility
    │
    ▼
Component
    │
    ▼
   API
    │
    ▼
User journey
Text
    Unit
     │
     ▼
Integration
     │
     ▼
 End-to-end

Different testing approaches answer different questions, but together they provide confidence that the application behaves correctly from individual functions to complete user journeys.

Colocated tests

Tests live next to the files they verify.

Text
VueButton.vue
VueButton.stories.ts
VueButton.spec.ts
Text
index.get.ts
index.get.spec.ts

This keeps implementation and tests close together and avoids switching constantly between separate source and test directory trees.

Colocation also makes ownership immediately visible. When a file changes, its corresponding tests are easy to find.

Frontend tests

Frontend tests use Vitest together with Testing Library.

They interact with components through the same visible interface available to users:

  • Accessible roles
  • Labels
  • Text
  • Buttons
  • Form controls
  • User interactions

A test should prefer:

TypeScript
await ui.click(ui.getByRole('button', { name: 'Save' }));

over assertions against internal component state or private methods.

This allows components to be refactored without rewriting tests as long as their observable behavior remains unchanged.

Story-based component tests

Component scenarios are first defined as Storybook stories.

Once the behavior of a story is satisfactory, its arguments are reused as the starting point for the automated test.

Text
Component
  │
  ▼
Story args
├── Interactive documentation
├── Manual review
└── Automated test setup

This avoids maintaining one set of example data for Storybook and another for Vitest.

A component test imports the relevant story and passes its args to the shared component harness:

TypeScript
import { Identify } from './AuthForm.stories';
import { getHarnessForComponent } from '@test/test-utils';

describe('AuthForm.vue', () => {
  it('renders the identify form', () => {
    const ui = getHarnessForComponent(AuthForm, {
      props: { ...Identify.args },
    });

    expect(ui.getByLabelText('input.email.label*')).toBeTruthy();
    expect(ui.getByText('common.Continue')).toBeTruthy();
  });
});

The story defines the visible scenario, while the test verifies its behavior through the same public interface available to users.

Stories remain focused on representative component states. Tests extend those states with interactions and assertions.

This keeps documentation and test setup aligned without duplicating component scenarios.

Server tests

Server tests verify complete HTTP request flows.

Instead of calling controllers or services directly, tests send real requests through the server API.

A typical server test covers:

Text
    HTTP request
         │
         ▼
  Input validation
         │
         ▼
     Controller
         │
         ▼
Authorization policy
         │
         ▼
      Service
         │
         ▼
       Prisma
         │
         ▼
     PostgreSQL

This verifies that the layers work together rather than proving only that an isolated function returns an expected value.

Server tests use a real PostgreSQL database. Test data is created specifically for the scenario being tested and removed or isolated through the test setup.

While small unit tests remain useful for isolated utilities and domain logic, most application behavior is verified through integration tests.

Test data

Backend tests should create the data required by their own scenario.

Avoid relying on one shared demo user or a large global fixture whose state is modified by many unrelated tests.

Tests should be independent from one another.

Running a single test or the entire suite should produce the same result.

Local test data provides:

  • Clear preconditions
  • Better isolation
  • Easier debugging
  • Less coupling between tests

Factories and helper functions may reduce repetitive setup, but the test should still make its relevant state understandable.

End-to-end tests

End-to-end tests use Playwright to verify complete user journeys in a real browser.

They run against the complete application and a dedicated E2E database that is separate from the database used by server integration tests.

Typical E2E scenarios include:

  • Signing in
  • Registering an account
  • Navigating between protected pages
  • Completing an important product workflow

Vuesion intentionally keeps the included E2E suite small.

End-to-end testing strategy depends heavily on the product, team, deployment process, and acceptable execution time. Projects should extend the suite around their own most important user journeys rather than attempting to reproduce every lower-level test in the browser.

Mock as little as practical

Vuesion prefers real integrations whenever they remain reliable and reasonably fast.

That means using:

  • Real components
  • Real HTTP requests
  • Real PostgreSQL databases
  • Real user interactions

Mocks are still necessary at some external boundaries, such as third-party APIs, email delivery, OAuth providers, browser APIs, or services that cannot safely run during tests.

The goal is not to eliminate mocking.

The goal is to mock the boundaries of the system, not the system itself.

Coverage

Vuesion aims to maintain 100% code coverage.

This is not about reaching an arbitrary number.

It is about making every uncovered line an intentional decision.

If code should temporarily remain uncovered, that decision should be visible directly in the source code:

TypeScript
/* v8 ignore start */

Explicit coverage exclusions can easily be found and reviewed later through a simple code search.

The goal is not to claim that every test is equally valuable.

The goal is to prevent untested code from accumulating invisibly as the project grows.

Why this approach?

Tests become difficult to maintain when they are separated from their source files, coupled to implementation details, dependent on shared mutable fixtures, or built mostly from mocks.

Vuesion instead favors:

  • Colocated tests
  • Observable behavior
  • Story-based component scenarios
  • Real HTTP requests
  • Real database integration
  • Minimal mocking
  • A focused E2E suite
  • Explicit coverage decisions

The result is a test suite that supports refactoring, encourages confidence, and catches integration problems without requiring every scenario to be repeated at every layer.

Next steps

Continue with Generators to learn how Vuesion creates consistent foundations for domains, APIs, components, and pages.