RealWorld API Testing: A Complete Guide to Hurl, Bruno, and Playwright
The RealWorld reference implementation employs a multi-layered testing strategy using Hurl for declarative API contract validation, Bruno as a GUI/CLI alternative for the same endpoints, and Playwright for end-to-end UI validation, all anchored by a canonical OpenAPI 3.1 specification.
The gothinkster/realworld repository serves as the standard specification for building Medium.com clone applications, making robust RealWorld API testing essential for backend verification. The official test suite validates that any implementation correctly handles authentication, articles, comments, and profiles according to the strict OpenAPI contract. Developers can verify their backends using command-line HTTP testing tools or comprehensive browser automation.
API Contract Validation with Hurl
Hurl provides the primary command-line interface for validating API conformance in the RealWorld ecosystem. The test runner located at specs/api/hurl/run-hurl-tests.sh orchestrates execution of all *.hurl files against your backend implementation.
The script discovers every .hurl file in the specs/api/hurl/ directory and executes them with deterministic ordering using hurl --jobs 1. This single-threaded approach ensures tests run sequentially rather than in parallel, preventing race conditions in shared test data.
Execute the full Hurl suite against a local backend:
# Default host is http://localhost:8000; override with HOST variable
HOST=http://localhost:8000 ./specs/api/hurl/run-hurl-tests.sh
Each .hurl file contains declarative HTTP requests paired with assertions. The file specs/api/hurl/tags.hurl demonstrates the concise syntax:
GET {{host}}/api/tags
HTTP 200
[Asserts]
jsonpath "$.tags" length > 0
The {{host}} variable injects your target URL, while JSONPath assertions verify response structure and content without requiring a programming language runtime.
API Contract Validation with Bruno
Bruno offers a GUI-compatible alternative for the same API contract validation, storing requests in structured .bru files rather than plain text. The entry point specs/api/run-api-tests-bruno.sh launches the Bruno CLI against collections organized under specs/api/bruno/.
Bruno collections mirror the Hurl test coverage but provide additional workflow features like folder organization and a desktop interface for manual exploration. The runner script automatically generates a unique UID_VAL environment variable to isolate test data between runs.
Install the Bruno CLI and execute the collection:
npm i -g @usebruno/cli
HOST=http://localhost:8000 ./specs/api/run-api-tests-bruno.sh
The script passes the HOST and UID_VAL variables to the Bruno runner, which executes requests defined in files like specs/api/bruno/tags/01-setup-register.bru. Both Hurl and Bruno validate the same endpoints defined in specs/api/openapi.yml, allowing teams to choose based on workflow preferences.
End-to-End Testing with Playwright
While Hurl and Bruno verify API correctness, Playwright validates that frontend applications correctly consume these endpoints through real browser automation. The test configuration in specs/e2e/playwright.base.ts defines shared fixtures and timeouts for cross-browser testing.
Playwright tests spin up actual browser instances and simulate complete user flows including registration, article publication, commenting, and favorites. By default, these tests target http://localhost:8000 for the backend API, though this can be configured via environment variables to test different implementations.
Install dependencies and run the E2E suite:
npm ci
npx playwright test
A typical scenario from specs/e2e/auth.spec.ts validates the authentication flow:
test('user can register and login', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.getByRole('link', { name: 'Sign up' }).click();
await page.fill('input[placeholder="Username"]', `user${uid}`);
await page.fill('input[placeholder="Email"]', `user${uid}@example.com`);
await page.fill('input[placeholder="Password"]', 'Password123');
await page.click('button:has-text("Sign up")');
await expect(page).toHaveURL(/\/@user/);
});
These tests verify both API functionality and UI integration, catching issues that unit tests might miss, such as cookie handling or redirect behaviors.
The OpenAPI Specification Foundation
All testing layers derive from the canonical contract defined in specs/api/openapi.yml, an OpenAPI 3.1 document that specifies every endpoint, request schema, response code, and security scheme. This specification serves as the single source of truth for both automated and manual testing.
Hurl and Bruno collections map directly to operations defined in this specification, ensuring that any backend implementation passing these tests conforms to the official RealWorld API contract. The OpenAPI file also enables automatic client generation and documentation synchronization.
Legacy Postman Support
For teams maintaining existing Postman workflows, the repository includes specs/api/legacy_Conduit.postman_collection.json. This JSON export provides the same request definitions as the Hurl and Bruno suites, though the project actively maintains the newer CLI-first tools as the recommended path for automated testing.
Summary
- Hurl tests in
specs/api/hurl/provide fast, deterministic CLI validation using plain-text files with JSONPath assertions executed viarun-hurl-tests.sh. - Bruno collections under
specs/api/bruno/offer a GUI-compatible alternative with the same coverage, executed throughrun-api-tests-bruno.shwith automatic test data isolation. - Playwright E2E tests in
specs/e2e/verify frontend-backend integration using real browser automation against running applications. - All testing layers reference
specs/api/openapi.ymlas the authoritative contract, ensuring consistent validation across different backend implementations.
Frequently Asked Questions
What is the difference between Hurl and Bruno for RealWorld API testing?
Hurl uses simple plain-text files with .hurl extensions and excels in CI/CD pipelines with minimal dependencies, while Bruno uses structured .bru files and provides both a command-line interface and a desktop GUI for interactive debugging. Both tools validate the same OpenAPI endpoints and can be used interchangeably depending on whether your workflow prioritizes text-based version control or visual request building.
How do I run RealWorld API tests against a custom backend URL?
Both the Hurl and Bruno test scripts accept a HOST environment variable to target any backend implementation. For Hurl, run HOST=https://your-api.com ./specs/api/hurl/run-hurl-tests.sh. For Bruno, run HOST=https://your-api.com ./specs/api/run-api-tests-bruno.sh. Playwright E2E tests can similarly target custom hosts through configuration variables in playwright.base.ts.
Does the RealWorld test suite support parallel execution?
The Hurl test runner explicitly uses --jobs 1 to enforce sequential execution and prevent test data conflicts, while Playwright runs browser tests in parallel by default across multiple workers. If you need deterministic ordering for debugging, run Playwright with npx playwright test --workers 1.
Can I use these tests to validate my own RealWorld backend implementation?
Yes, any backend that correctly implements the specs/api/openapi.yml specification can be validated using these tools. Simply start your server and point the HOST variable to your local or deployed instance. The tests verify exact status codes, response schemas, and error message formats required by the specification.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →