RizTech Academy logo
RizTech Academy
API AutomationLesson 1 of 430 min

Automating API tests in code

You designed API test cases by hand in the API-testing module and ran them in Postman. Now you automate them — turn those requests and checks into code that runs on every change. API automation is the highest-value automation there is (the test pyramid: fast, stable, close to the logic), and it is easier than UI automation because there is no browser, no waiting for screens, just requests and responses. This lesson is automating API tests in code, using AgentPay's real API suite as the model.

Why automate at the API layer first

Recall the pyramid: API tests sit in the sweet spot — more realistic than unit tests, far faster and more stable than UI tests. They are where you get the most reliable coverage per hour of effort:

  • Fast — a request and a response, milliseconds each; no browser to start, no page to render. AgentPay's 21 API tests run in about two seconds.
  • Stable — an API changes less than a UI, and there is no flakiness from timing, animations or slow renders. API tests almost never flake.
  • Close to the logic — you test validation, permissions, calculations and money flows directly where they live, not through layers of UI.

So when you start automating, automate the API tests first. They pay back fastest and rot slowest.

The tools

You write API tests in a test runner — the same kind of framework as unit tests. AgentPay uses Vitest (a fast, modern runner; Jest is the near-identical classic; any language has an equivalent). You need two things: a way to send requests and a way to assert on responses.

For sending requests you have choices:

  • The framework's built-in injector — many web frameworks can handle a request in-process without a real network. AgentPay uses Fastify's app.inject(...), which is extremely fast and needs no running server.
  • A real HTTP client — fetch, axios, supertest, or Playwright's request fixture — against a running instance. Use this when you want to test the real network path or a deployed environment.

Both are valid; in-process injection is fastest for testing your own app, a real client is right for testing a deployed API. The assertions are the same either way.

The shape of an API test

Every API test follows Arrange, Act, Assert (the pattern from any testing):

import { describe, it, expect, beforeEach } from 'vitest';
import { buildApp } from './app.js';
import { reset } from './db.js';

let app;
beforeEach(() => { reset(); app = buildApp(); }); // Arrange: fresh app, known data

it('creates a transaction and returns 201 with the record', async () => {
  const token = await tokenFor('admin@agentpay.test', 'admin123');
  const res = await app.inject({                    // Act: send the request
    method: 'POST',
    url: '/transactions',
    headers: { authorization: `Bearer ${token}` },
    payload: { agentId: 1, amount: 500, phone: '9876543210', type: 'payment' },
  });
  expect(res.statusCode).toBe(201);                 // Assert: status…
  expect(res.json()).toMatchObject({ agentId: 1, amount: 500, status: 'success' }); // …and body
});

Read it against what you did in Postman: choose method, URL, headers, body; send; check status and body. The automated version is the same test, written as code so it runs a thousand times for free. The beforeEach(reset) is the api-automation version of the fixtures lesson — every test starts from AgentPay's known seed, so tests are independent.

A helper for auth

Most API tests need a token, so — exactly like the UI suite's loginAs — you extract that into a helper so no test repeats the login:

async function tokenFor(email: string, password: string) {
  const res = await app.inject({ method: 'POST', url: '/auth/login', payload: { email, password } });
  return res.json().token;
}

Now every test starts const token = await tokenFor(...) and passes it in the Authorization header. This is the same DRY principle as page objects: the knowledge of "how to authenticate" lives in one place.

Turning your Postman cases into code

The workflow is direct: each case you designed and tried in Postman becomes one automated test. The positive case (valid input → 201 + correct body), and each negative case (invalid input → the right 4xx). AgentPay's suite is exactly your manual cases, automated:

  • Login valid → 200 + token; wrong password → 401; malformed body → 400.
  • Create valid → 201; amount 0 → 422; amount over limit → 422; bad phone → 422; suspended agent → 409.
  • No token → 401; invalid token → 401.

Your manual test design is not wasted when you automate — it is the automation. The thinking you did to choose the cases is the hard part; writing them as app.inject calls is mechanical. That is why the api-testing module came first: automation without good test design just runs bad checks quickly.

Check your work

Automate the API first. It is the pyramid's sweet spot — fast (AgentPay's 21 tests in ~2s), stable (no timing/render flakiness), and close to the logic. Highest value per hour, slowest to rot.

Tools: a test runner (Vitest/Jest/equivalent) plus a way to send requests — the framework's in-process injector (AgentPay's app.inject, fastest for your own app) or a real HTTP client (fetch/supertest/ Playwright request, for a deployed API). Same assertions either way.

Shape: Arrange (fresh app + known data via beforeEach(reset)), Act (send the request), Assert (status and body). It is your Postman case written as code. Extract auth into a tokenFor helper (DRY, like loginAs/page objects).

Your manual cases become the tests — the positive case and each negative 4xx you designed in Postman, now automated. Good test design is the automation; writing it as code is the mechanical part.

Practice

  1. Set up Vitest (or Jest) in the AgentPay API and run its existing app.test.ts.
  2. Write an API test for POST /transactions valid case: assert 201 and the body with toMatchObject.
  3. Add a tokenFor helper and use it across three tests instead of repeating the login.
  4. Automate three negative cases from your Postman work (missing field, over-limit amount, no token) and assert the right status each.
  5. Add beforeEach(reset) and show that a test which creates a transaction does not affect the next test.
  6. Explain why automating the API tests is higher value than automating the same checks through the UI.

Official documentation

Next: asserting status, body and schema.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship