(greatest) Open Api Test library of all times!
The oat
package makes it very easy to test your API via an OpenAPI specification. It:
- 🤝 validates request and response parameter
- 🧬 generates multiple tests based on different security or parameter combinations
- 🧩 is compatible to all JavaScript testrunner
- 🚀 integration to Faker.js for easy payload generation
The following example shows a simple API test using Vitest:
import { describe, it } from 'vitest'
import { Testplan, URLParam, APIKeyAuth } from 'oat'
import type { OpenAPIV3 } from 'openapi-types'
import { specification } from './openapi-specification.json' assert { type: 'json' }
const urlParam = new URLParam({ id: '39f07889-1072-48df-8ca6-9d6726b5e525' })
const apiToken = new APIKeyAuth('Authorization', 'codeless-qa-b82b312d-4d44-40a3-bb5a-02529417e2d7', 'header')
describe('/api/specifications/{id}', () => {
const plan = new Testplan(specification as OpenAPIV3.Document)
plan.runTest('delete', '/api/specifications/{id}')
.withPayloads(urlParam)
.expect(401) // fails due to missing auth
plan.runTest('get', '/api/specifications/{id}')
.withSecuritySchemes([apiToken])
.withPayloads(urlParam)
.expect(200)
plan.runWith(it)
})
Install the package via:
npm i oat
The following primitives are available for composing your API tests.
A test plan composes one or multiple API tests based on provided security schemas and payload. To create an instance pass in an OpenAPI v3 specification as payload.
const plan = new Testplan(specification)
Creates a test penetrating a specific endpoint that can be contain different security schemas or payloads. The method returns an instance of a Test
.
plan.runTest('get', '/api/specifications/{id}')
Select a server from the specification to run the test agains, e.g. given the following server defintion:
{
"servers": [
{
"url": "https://staging.gigantic-server.com/v1",
"description": "Staging server"
},
{
"url": "https://{username}.gigantic-server.com:{port}/{basePath}",
"description": "The production API server",
"variables": {
"username": {
"default": "demo",
"description": "this value is assigned by the service provider, in this example `gigantic-server.com`"
},
"port": {
"enum": [
"8443",
"443"
],
"default": "8443"
},
"basePath": {
"default": "v2"
}
}
}
]
}
You can select a server either via the index or the URL, e.g.:
plan.usingServer(0) // selects Staging server
plan.usingServer('https://staging.gigantic-server.com/v1') // selects custom server
plan.usingServer(1, { username: 'demo', port: '8443', basePath: 'v2' }) // selects server with parameter
Test function of the test framework of your choice, e.g. Vitest, Mocha etc. The provided test function should match the following interface:
type FrameworkFn = ((title: string, fn: (() => void)) => unknown)
type FrameworkAPI = FrameworkFn & {
only?: FrameworkFn
skip?: FrameworkFn
}
For above mentioned frameworks, it would be simply:
import { describe, it } from 'vitest'
plan.runWith(it)
Every test plan allows to register hooks to execute synchronous or asynchronous code at different lifecycle moments of a test.
The beforeRequest
hook will be executed before a test, there therefore a request is being made. It allows to modify the request url and payload.
Type: (options: { url: string, requestInit: RequestInit }) => void | Promise<void>
Example:
const plan = new Testplan(specification, {
/**
* set custom test header
*/
beforeRequest: async ({ url, options }) => {
(options.headers as Record<string, string>)['x-test-api'] = '1'
}
})
The afterResponse
hook allows you to modify the response before it is processed and validated, e.g. to log the response.
Type: (options: { url: string, requestInit: RequestInit }) => void | Promise<void>
Example:
const plan = new Testplan(specification, {
/**
* set custom test header
*/
afterResponse: async (response, { url, options }) => {
console.log(`Received response from ${url} with status ${response.status}`)
}
})
A test represents one or multiple API request to a certain endpoint.
Allows to attach one or multiple security schemas to the test plan.
import { Testplan, APIKeyAuth } from 'oat'
const apiToken = new APIKeyAuth('Authorization', 'codeless-qa-b82b312d-4d44-40a3-bb5a-02529417e2d7', 'header')
const plan = new Testplan(specification)
plan.runTest('delete', '/api/specifications/{id}')
.withSecuritySchemes([apiToken])
Allows to attach one or multiple payload schemas to the test plan.
import { Testplan, URLParam, BodyPayload } from 'oat'
const urlParam = new URLParam({ id: '39f07889-1072-48df-8ca6-9d6726b5e525' })
const tokenPayload = new BodyPayload({ name: 'foobar', expires: null })
const plan = new Testplan(specification as OpenAPIV3.Document)
plan.runTest('delete', '/api/specifications/{id}')
.withPayloads([urlParam, tokenPayload])
Defines the expected status code and therefor response format.
import { Testplan } from 'oat'
const plan = new Testplan(specification)
plan.runTest('delete', '/api/specifications/{id}').expect(401)
A security object to be passed into a withSecuritySchemes
function. Oat will create individual test for every security object passed into withSecuritySchemes
.
Oat supports the following auth mechanism:
- Basic auth via
BasicAuth
class - Bearer tokens via
BearerAuth
class - Custom Headers via
APIKeyAuth
class
If called on the object, all test containing this security object will be skipped.
import { Testplan, APIKeyAuth } from 'oat'
const apiToken = new APIKeyAuth('Authorization', 'codeless-qa-b82b312d-4d44-40a3-bb5a-02529417e2d7', 'header')
apiToken.skip()
const plan = new Testplan(specification)
plan.runTest('delete', '/api/specifications/{id}')
.withSecuritySchemes([apiToken]) // test will be skipped
Extends from SecuritySchemeObject
.
A security object that represents a Basic Authentification header key.
import { BasicAuth } from 'oat'
const basicAuth = new BasicAuth('admin', 'password')
Extends from SecuritySchemeObject
.
A security object that represents a bearer token key.
import { BearerAuth } from 'oat'
const basicAuth = new BearerAuth('codeless-qa-b82b312d-4d44-40a3-bb5a-02529417e2d7')
You can extract a value from a request defined in the same specification using its operationId
as a value for the token, e.g.:
import { BearerAuth } from 'oat'
/**
* Given you have an operation defined as following:
* ```ts
* {
* "operationId": "createUser",
* "response": {
* "201": {
* "content": {
* "application/json": {
* "schema": {
* "type": "object",
* "properties": {
* "data": {
* "type": "object",
* "properties": {
* "user": { ... }
* "token": { "type": "string" }
* }
* }
* }
* }
* }
* }
* }
* }
* }
* ```
* You can reference the value of the response of that request via `#/<operationId>/<statusCode>/<scope>/<path to property>`.
*/
new BearerAuth('#/createUser/201/body/data.token') // get token from body payload of `createUser` response
new BearerAuth('#/createUser/201/header/authorization') // get token from "Authorization" header of `createUser` response
new BearerAuth('#/createUser/201/cookie/sessionId') // get token from "sessionId" cookie of `createUser` response
Supported scopes are body
, header
and cookie
.
Note: you must have a test that delivers that response defined and run first, otherwise Oat won't be able to resolve the value and will throw an error.
Extends from SecuritySchemeObject
.
A security object that represents a key/value header pair.
import { APIKeyAuth } from 'oat'
const apiToken = new APIKeyAuth('Authorization', 'codeless-qa-b82b312d-4d44-40a3-bb5a-02529417e2d7')
Extends from SecuritySchemeObject
.
Allows to combine multiple security schemas for a single test, e.g. when an endpoint requires multiple auth mechanism at once.
import { CombinedSecuritySchemes, APIKeyAuth, BasicAuth } from 'oat'
const authMethodHeader = new APIKeyAuth('x-auth-method', 'basic-auth')
const basicAuth = new BasicAuth('admin', 'password')
const combinedSecScheme = new CombinedSecuritySchemes([ authMethodHeader, basicAuth ])
A payload object to be passed into a withPayloads
function. Oat will create individual test for every payload object passed into withPayloads
.
Oat supports the following payloads:
- URL query parameter via
QueryParam
class - URL path parameter via
URLParam
class - Body payload via
BodyPayload
class
If called on the object, all test containing this security object will be skipped.
import { Testplan, APIKeyAuth } from 'oat'
const urlParam = new URLParam({ id: '39f07889-1072-48df-8ca6-9d6726b5e525' })
urlParam.skip()
const plan = new Testplan(specification)
plan.runTest('delete', '/api/specifications/{id}')
.withPayloads([urlParam]) // test will be skipped
Extends from PayloadObject
.
A payload object to define query parameters.
import { Testplan, QueryParam } from 'oat'
const queryParam = new QueryParam({
name: 'foobar',
type: 'token'
})
const plan = new Testplan(specification)
plan.runTest('delete', '/api/specifications/{id}')
.withPayloads([queryParam]) // creates a request to "/api/specifications/{id}?name=foobar&type=token"
Extends from PayloadObject
.
A payload object to define parameters within the url.
import { Testplan, URLParam } from 'oat'
const urlParam = new URLParam({ id: '39f07889' })
const plan = new Testplan(specification)
plan.runTest('delete', '/api/specifications/{id}')
.withPayloads([urlParam]) // creates a request to "/api/specifications/39f07889"
Extends from PayloadObject
.
Sets a request body payload. If your request payload is a JSON you can define object properties through the Faker.js API via #/faker/<module>/<type>
, e.g. a field with #/faker/internet/email
will be replaced with a random email.
import { Testplan, BodyPayload } from 'oat'
const jsonBody = new BodyPayload({
type: 'user',
/**
* will be replaced with the value returned by `faker.internet.username()`, e.g. "Nettie_Zboncak40"
*/
name: '#/faker/internet/username',
/**
* will be replaced with the value returned by `faker.date.birthdate()`, e.g. "1977-07-10T01:37:30.719Z"
*/
birthday: '#/faker/date/birthdate'
})
const streamBody = new BodyPayload(Buffer.from('...'))
const plan = new Testplan(specification)
plan.runTest('delete', '/api/specifications/{id}')
.withPayloads([jsonBody, streamBody])
Extends from PayloadObject
.
Allows to combine multiple payload objects for a single test, e.g. when an endpoint contains an url parameter and body payload.
import { CombinedPayload, URLParam, BodyPayload } from 'oat'
const jsonBody = new BodyPayload({ some: 'payload' })
const urlParam = new URLParam({ id: '39f07889' })
const combinedPayload = new CombinedPayload([ jsonBody, jsonBody ])
Apache 2 License © 2024-PRESENT CodelessQA