REST API Testing
See how to create tests for REST APIs applications in Athenna.
Introduction
Athenna provides a very fluent API for making HTTP requests to your application and examining the responses. For example, take a look at the e2e test defined below:
import { Test, type Context } from '@athenna/test'
import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest'
export default class ExampleTest extends BaseHttpTest {
@Test()
public async 'test successful response'({ request }: Context) {
const response = await request.get('/')
response.assertStatusCode(200)
}
}
The request.get()
method makes a GET
request into the application,
while the assertStatusCode()
method asserts that the returned
response should have the given HTTP status code. In addition
to this simple assertion, Athenna also contains a variety of
assertions for inspecting the response headers, content,
JSON structure, and more.
You might have noticed that the ExampleTest
is extending the
BaseHttpTest
class. We gonna see later on this documentation
what is the purpose of this class, and how to configure it for your
needs.
Registering request
plugin
The request
property in your test context will only be
available if you register the request plugin within the
Runner
class. By default, your Athenna application already
comes with the request plugin registered. But we are going
to cover how to register it manually if needed.
Just call the Runner.addPlugin()
static method to setup
the request plugin imported from @athenna/http/testing/plugins
:
import { Runner } from '@athenna/test'
import { request } from '@athenna/http/testing/plugins'
import { command } from '@athenna/artisan/testing/plugins'
await Runner.setTsEnv()
.setAppEnv('test')
.addAssertPlugin()
.addPlugin(request()) 👈
.addPlugin(command())
.addPath('tests/e2e/**/*.ts')
.addPath('tests/unit/**/*.ts')
.setCliArgs(process.argv.slice(2))
.setGlobalTimeout(5000)
.run()
Making Requests
To make a request to your application, you may invoke the
request.get()
, request.post()
, request.put()
,
request.patch()
, or request.delete()
methods
within your test. These methods do not actually issue a "real"
HTTP request to your application. Instead, the entire network
request is simulated internally.
Instead of returning a response instance, test request methods
return an instance of TestResponse
, which provides a variety
of helpful assertions
that allow you to inspect your application's
responses:
import { Test, type Context } from '@athenna/test'
import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest'
export default class ExampleTest extends BaseHttpTest {
@Test()
public async testBasicRequest({ request }: Context) {
const response = await request.get('/')
response.assertStatusCode(200)
}
}
Customizing request
All the request methods accept a second argument where you can setup the request options:
import { type InjectOptions } from '@athenna/http'
import { Test, type Context } from '@athenna/test'
import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest'
export default class ExampleTest extends BaseHttpTest {
@Test()
public async testBasicRequest({ request }: Context) {
const options: InjectOptions = {
headers: {
'X-Header': 'value'
},
body: {
foo: 'bar'
}
}
const response = await request.get('/', options)
response.assertStatusCode(200)
}
}
Debugging responses
After making a test request to your application, the response
returned will contain the response
property inside with all the
response data:
import { Test, type Context } from '@athenna/test'
import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest'
export default class ExampleTest extends BaseHttpTest {
@Test()
public async testBasicRequest({ request }: Context) {
const response = await request.get('/')
console.log(response.response.status)
response.assertStatusCode(200)
}
}
Testing file uploads
Coming soon with @athenna/storage
package!
Response assertions
Athenna's TestResponse
class provides a variety of custom
assertion methods that you may utilize when testing your
application. These assertions may be accessed on the
response that is returned by the request.get()
, request.post()
,
request.put()
, request.patch()
, or request.delete()
test methods:
assertStatusCode()
assertBodyContains()
assertBodyContainsKey()
assertBodyContainsAllKeys()
assertBodyDeepEqual()
assertBodyIsArray()
assertBodyIsObject()
assertHeaderContains()
assertHeaderContainsKey()
assertHeaderContainsAllKeys()
assertHeaderDeepEqual()
assertStatusCode()
Assert that the response has a given HTTP status code:
const statusCode = 200
response.assertStatusCode(statusCode)
response.assertIsNotStatusCode(statusCode)
assertBodyContains()
Assert that the response body contains some of the given values:
const partialBody = { hello: 'world' }
response.assertBodyContains(partialBody)
response.assertBodyNotContains(partialBody)
assertBodyContainsKey()
Assert that the response body contains a key with the given name:
const key = 'hello'
response.assertBodyContainsKey(key)
response.assertBodyNotContainsKey(key)
assertBodyContainsAllKeys()
Assert that the response body contains all the keys with given names:
const keys = ['hello']
response.assertBodyContainsAllKeys(keys)
response.assertBodyNotContainsAllKeys(keys)
assertBodyDeepEqual()
Assert that the response body is deep equal and exactly like the given value:
const fullBody = [{ hello: 'world' }]
response.assertBodyDeepEqual(fullBody)
response.assertBodyNotDeepEqual(fullBody)
assertBodyIsArray()
Assert that the response body is an array:
response.assertBodyIsArray()
response.assertBodyIsNotArray()
assertBodyIsObject()
Assert that the response body is an object:
response.assertBodyIsObject()
response.assertBodyIsNotObject()
assertHeaderContains()
Assert that the response header contains some of the given values:
const partialHeader = { hello: 'world' }
response.assertHeaderContains(partialHeader)
response.assertHeaderNotContains(partialHeader)
assertHeaderContainsKey()
Assert that the response header contains a key with the given name:
const key = 'hello'
response.assertHeaderContainsKey(key)
response.assertHeaderNotContainsKey(key)
assertHeaderContainsAllKeys()
Assert that the response header contains all the keys with given names:
const keys = ['hello']
response.assertHeaderContainsAllKeys(keys)
response.assertHeaderNotContainsAllKeys(keys)
assertHeaderDeepEqual()
Assert that the response header is deep equal and exactly like the given value:
const fullHeader = [{ hello: 'world' }]
response.assertHeaderDeepEqual(fullHeader)
response.assertHeaderNotDeepEqual(fullHeader)
The BaseHttpTest
class
The BaseHttpTest
class is responsible to bootstrap your Athenna
application before running all tests and also to kill the
application after running all tests, meaning that is not possible
to use the request
property without extending this class or at least
setting up your own Athenna application using Ignite
class.
If for some reason you need to change the options set when
calling the Ignite.load()
or Ignite.httpServer()
methods,
you can set the igniteOptions
and httpOptions
properties
in your test class:
import { Path } from '@athenna/common'
import { Test, type Context } from '@athenna/test'
import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest'
import { type HttpOptions, type IgniteOptions } from '@athenna/core'
export default class ExampleTest extends BaseHttpTest {
public httpOptions: HttpOptions = {
host: '0.0.0.0',
port: 9999,
trace: true,
routePath: Path.fixtures('routes/routes.ts'),
kernelPath: Path.fixtures('kernels/Kernel.ts'),
exceptionHandlerPath: Path.fixtures('handlers/Handler.ts')
}
public igniteOptions: IgniteOptions = {
bootLogs: true,
shutdownLogs: true,
envPath: Path.fixtures('envs/.env'),
athennaRcPath: Path.fixtures('rcs/.athennarc.json'),
environments: ['http', 'test']
}
@Test()
public async 'test successful response'({ request }: Context) {
const response = await request.get('/')
response.assertStatusCode(200)
}
}
Accessing Ignite
and Http
instance
You are able to access the Ignite
and Http
instances by
using the ignite
and httpServer
properties:
import { Test, type Context } from '@athenna/test'
import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest'
export default class ExampleTest extends BaseHttpTest {
@Test()
public async 'test successful response'({ request }: Context) {
this.ignite 👈
this.httpServer 👈
}
}