Skip to content

Latest commit

 

History

History
65 lines (52 loc) · 2.59 KB

File metadata and controls

65 lines (52 loc) · 2.59 KB
title Testing APIs
description Check your API with fetch and value matchers, in the same files and run as UI tests.

API tests run in the same files and the same run as UI tests. A test that takes only app calls no model. Call the API with fetch and check the response with the value matchers.

import { test, expect } from 'e2e';
import { z } from 'zod';

const User = z.object({ id: z.number(), name: z.string() });

test('GET /api/users returns the seeded users', async ({ app }) => {
  const response = await fetch(new URL('/api/users', app.baseUrl));
  expect(response.status).toBe(200);
  expect(response.headers.get('content-type')).toContain('application/json');
  const users = expect(await response.json()).toMatchSchema(z.array(User));
  expect(users).toHaveLength(3);
  expect(users.map((user) => user.name)).toEqual(['Ada', 'Grace', expect.any(String)]);
});

toMatchSchema takes any Standard Schema (Zod, Valibot, ArkType), so the schema your app parses the API with checks that the API still matches it, and it returns the value typed.

Where the API runs

app.baseUrl is the target's app.url, with the port the run allocated when it declared port 0, so the same file runs against the app.command the config starts and against a deployed preview. For state the API writes after it answers, expect.poll re-reads until a matcher passes.

Signed-in requests

To call the API as a signed-in user, write a fixture that sends browser.cookies() with each request. Every test registered through this test acquires browser, so it needs a web target:

import { test as base } from '@e2e-dev/web';

export const test = base.extend<{ api: (path: string, init?: RequestInit) => Promise<Response> }>({
  api: async ({ app, browser }, use) => {
    await use(async (path, init) => {
      const url = new URL(path, app.baseUrl);
      if (url.origin !== new URL('/', app.baseUrl).origin) throw new Error(`${url} is not on the app`);
      const headers = new Headers(init?.headers);
      headers.set('cookie', (await browser.cookies()).map((c) => `${c.name}=${c.value}`).join('; '));
      return fetch(url, { ...init, headers });
    });
  },
});

Without a browser

On a web target, an API test still opens a browser context. A target without an engine opens nothing, but it has no app.baseUrl, so read the URL from an environment variable. e2e init sets up such a target when you choose None at the engine prompt.