From 21cea04f2bd307490a3fbf9b44c6188c61b93ea6 Mon Sep 17 00:00:00 2001 From: jlenon7 Date: Sun, 11 Feb 2024 11:05:33 +0000 Subject: [PATCH] feat(testing): add more content --- docs/orm/annotations.mdx | 2 +- docs/testing/cli-tests.mdx | 124 +++++++++++++++++++++++++++--- docs/testing/getting-started.mdx | 20 +++-- docs/testing/rest-api-testing.mdx | 87 +++++++++++++++++++-- 4 files changed, 209 insertions(+), 24 deletions(-) diff --git a/docs/orm/annotations.mdx b/docs/orm/annotations.mdx index faf65e44..c39c10cb 100644 --- a/docs/orm/annotations.mdx +++ b/docs/orm/annotations.mdx @@ -94,7 +94,7 @@ null before running your model `create()`, `createMany()`, public deletedAt: Date ``` -:::warn +:::warning The value set to `defaulTo` property will only be used when the value for the specified column was not provided when calling diff --git a/docs/testing/cli-tests.mdx b/docs/testing/cli-tests.mdx index d9258043..d777d8ca 100644 --- a/docs/testing/cli-tests.mdx +++ b/docs/testing/cli-tests.mdx @@ -16,8 +16,9 @@ For example, take a look at the e2e test defined below: ```typescript import { Test, type Context } from '@athenna/test' +import { BaseConsoleTest } from '@athenna/core/testing/BaseConsoleTest' -export default class ExampleTest { +export default class ExampleTest extends BaseConsoleTest { @Test() public async 'test successful output'({ command }: Context) { const output = await command.run('app') @@ -35,6 +36,11 @@ output should have a successful exit code (`0`). In addition to this simple assertion, Athenna also contains a variety of assertions for inspecting the output. +You might have noticed that the `ExampleTest` is extending the +`BaseConsoleTest` class. We gonna see later on this documentation +what is the purpose of this class, and how to configure it for your +needs. + ## Registering `command` plugin The `command` property in your test context will only be @@ -47,15 +53,15 @@ Just call the `Runner.addPlugin()` static method to set up the request plugin imported from `@athenna/artisan/testing/plugins`: ```typescript title="Path.bootstrap('test.ts')" +import { Runner } from '@athenna/test' import { request } from '@athenna/http/testing/plugins' import { command } from '@athenna/artisan/testing/plugins' -import { Runner, assert, specReporter } from '@athenna/test' await Runner.setTsEnv() - .addPlugin(assert()) + .setAppEnv('test') + .addAssertPlugin() .addPlugin(request()) .addPlugin(command()) 👈 - .addReporter(specReporter()) .addPath('tests/e2e/**/*.ts') .addPath('tests/unit/**/*.ts') .setCliArgs(process.argv.slice(2)) @@ -75,8 +81,9 @@ that allow you to inspect your application's CLI output: ```typescript import { Test, type Context } from '@athenna/test' +import { BaseConsoleTest } from '@athenna/core/testing/BaseConsoleTest' -export default class ExampleTest { +export default class ExampleTest extends BaseConsoleTest { @Test() public async testBasicCommand({ command }: Context) { const output = await command.run('greet') @@ -98,17 +105,17 @@ used to test your commands. To do so, you can call the your tests: ```typescript title="Path.bootstrap('test.ts')" +import { Runner } from '@athenna/test' import { request } from '@athenna/http/testing/plugins' import { command, TestCommand } from '@athenna/artisan/testing/plugins' -import { Runner, assert, specReporter } from '@athenna/test' TestCommand.setArtisanPath(Path.fixtures('artisan.ts')) 👈 await Runner.setTsEnv() - .addPlugin(assert()) + .setAppEnv('test') + .addAssertPlugin() .addPlugin(request()) .addPlugin(command()) - .addReporter(specReporter()) .addPath('tests/e2e/**/*.ts') .addPath('tests/unit/**/*.ts') .setCliArgs(process.argv.slice(2)) @@ -128,8 +135,9 @@ this kind of customization in your tests: ```typescript import { Config } from '@athenna/config' import { Test, type Context } from '@athenna/test' +import { BaseConsoleTest } from '@athenna/core/testing/BaseConsoleTest' -export default class ExampleTest { +export default class ExampleTest extends BaseConsoleTest { @Test() public async testConfigCommand({ command }: Context) { Config.set('app.name', 'MyAppName') @@ -161,8 +169,9 @@ Now, we can use this new `artisan` file to run our command: ```typescript import { Path } from '@athenna/common' import { Test, type Context } from '@athenna/test' +import { BaseConsoleTest } from '@athenna/core/testing/BaseConsoleTest' -export default class ExampleTest { +export default class ExampleTest extends BaseConsoleTest { @Test() public async testConfigCommand({ command }: Context) { const output = await command.run('greet', { @@ -174,6 +183,32 @@ export default class ExampleTest { } ``` +### Changing Artisan file path for the test group + +If you need to use the same Artisan file for all the tests inside +of your class, but you don't want to change it globally for all +the rest of your classes using `TestCommand.setArtisanPath()` method, +you can set the `artisanPath` property in your test class that +`BaseConsoleTest` will automatically change it when bootstraping +your app: + +```typescript +import { Path } from '@athenna/common' +import { Test, type Context } from '@athenna/test' +import { BaseConsoleTest } from '@athenna/core/testing/BaseConsoleTest' + +export default class ExampleTest extends BaseConsoleTest { + public artisanPath = Path.fixtures('consoles/artisan-set-app-name.ts') 👈 + + @Test() + public async testConfigCommand({ command }: Context) { + const output = await command.run('greet') + + output.assertLogged('Hello from MyAppName!') ✅ + } +} +``` + ## Debugging outputs After executing a test command to your application, @@ -182,8 +217,9 @@ inside with all the `output` data: ```typescript import { Test, type Context } from '@athenna/test' +import { BaseConsoleTest } from '@athenna/core/testing/BaseConsoleTest' -export default class ExampleTest { +export default class ExampleTest BaseConsoleTest { @Test() public async testBasicCommand({ command }: Context) { const output = await command.run('basic') @@ -282,3 +318,69 @@ argument: output.assertLogMatches(/Hello World/, 'stdout') // or stderr output.assertLogNotMatches(/Hello World/, 'stdout') // or stderr ``` + +## The `BaseConsoleTest` class + +The `BaseConsoleTest` 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 `command` 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.artisan()` methods, +you can set the `igniteOptions` and `artisanOptions` properties +in your test class: + +```typescript +import { Path } from '@athenna/common' +import { type IgniteOptions } from '@athenna/core' +import { Test, type Context } from '@athenna/test' +import { BaseConsoleTest } from '@athenna/core/testing/BaseConsoleTest' + +export default class ExampleTest extends BaseConsoleTest { + 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 output'({ command }: Context) { + const output = await command.run('app') + + output.assertSucceeded() + } +} +``` + +:::warning + +Remember that changes done to the options of `Ignite` class will only +be relevant when running commands outside of the child process. Meaning +that if you call `command.run()`, the options used to ignite Artisan will +be from your Artisan file, and not from the ones you set in your test class. + +To solve this, check how to change the [Artisan file path of your +test group](/docs/testing/cli-tests#changing-artisan-file-path-for-the-test-group). + +::: + +### Accessing `Ignite` instance + +You are able to access the `Ignite` instance by using the `ignite` +property: + +```typescript +import { Test, type Context } from '@athenna/test' +import { BaseConsoleTest } from '@athenna/core/testing/BaseConsoleTest' + +export default class ExampleTest extends BaseConsoleTest { + @Test() + public async 'test successful output'({ command }: Context) { + this.ignite 👈 + } +} +``` diff --git a/docs/testing/getting-started.mdx b/docs/testing/getting-started.mdx index c194465c..60d71b16 100644 --- a/docs/testing/getting-started.mdx +++ b/docs/testing/getting-started.mdx @@ -42,13 +42,13 @@ node artisan test ## Environment -When running tests, Athenna will automatically set the -[configuration environment](/docs/getting-started/configuration) -to `test`, meaning that the `.env.test` file will be used when +By default Athenna comes set with the +[configuration environment](/docs/getting-started/configuration) +as `test`, meaning that the `.env.test` file will be used when running your tests. You are free to define other testing environment value. To do -that you can use the `setAppEnv()` method of `Runner` class +that you can simply change the `setAppEnv()` method of `Runner` class in your `Path.bootstrap('test.ts')` file: ```typescript @@ -57,10 +57,10 @@ import { request } from '@athenna/http/testing/plugins' import { command } from '@athenna/artisan/testing/plugins' await Runner.setTsEnv() + .setAppEnv('testing') 👈 .addAssertPlugin() .addPlugin(request()) .addPlugin(command()) - .setAppEnv('testing') 👈 .addPath('tests/e2e/**/*.ts') .addPath('tests/unit/**/*.ts') .setCliArgs(process.argv.slice(2)) @@ -68,6 +68,16 @@ await Runner.setTsEnv() .run() ``` +:::tip + +You can also use the `--env` flag to set the environment: + +```shell +node artisan test --env testing +``` + +::: + ## Writing tests To create a new test case, use the `make:test` Artisan diff --git a/docs/testing/rest-api-testing.mdx b/docs/testing/rest-api-testing.mdx index 453f1c62..263fe645 100644 --- a/docs/testing/rest-api-testing.mdx +++ b/docs/testing/rest-api-testing.mdx @@ -16,8 +16,9 @@ For example, take a look at the e2e test defined below: ```typescript import { Test, type Context } from '@athenna/test' +import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest' -export default class ExampleTest { +export default class ExampleTest extends BaseHttpTest { @Test() public async 'test successful response'({ request }: Context) { const response = await request.get('/') @@ -34,6 +35,11 @@ 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 @@ -46,15 +52,15 @@ Just call the `Runner.addPlugin()` static method to setup the request plugin imported from `@athenna/http/testing/plugins`: ```typescript title="Path.bootstrap('test.ts')" +import { Runner } from '@athenna/test' import { request } from '@athenna/http/testing/plugins' import { command } from '@athenna/artisan/testing/plugins' -import { Runner, assert, specReporter } from '@athenna/test' await Runner.setTsEnv() - .addPlugin(assert()) + .setAppEnv('test') + .addAssertPlugin() .addPlugin(request()) 👈 .addPlugin(command()) - .addReporter(specReporter()) .addPath('tests/e2e/**/*.ts') .addPath('tests/unit/**/*.ts') .setCliArgs(process.argv.slice(2)) @@ -79,8 +85,9 @@ responses: ```typescript import { Test, type Context } from '@athenna/test' +import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest' -export default class ExampleTest { +export default class ExampleTest extends BaseHttpTest { @Test() public async testBasicRequest({ request }: Context) { const response = await request.get('/') @@ -98,8 +105,9 @@ setup the request options: ```typescript import { type InjectOptions } from '@athenna/http' import { Test, type Context } from '@athenna/test' +import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest' -export default class ExampleTest { +export default class ExampleTest extends BaseHttpTest { @Test() public async testBasicRequest({ request }: Context) { const options: InjectOptions = { @@ -126,8 +134,9 @@ response data: ```typescript import { Test, type Context } from '@athenna/test' +import { BaseHttpTest } from '@athenna/core/testing/BaseHttpTest' -export default class ExampleTest { +export default class ExampleTest extends BaseHttpTest { @Test() public async testBasicRequest({ request }: Context) { const response = await request.get('/') @@ -287,3 +296,67 @@ 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: + +```typescript +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: + +```typescript +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 👈 + } +} +```