Testing
The framework comes with Vitest support. When you name files with the .test.(js|ts)
extension, they will be added to the tests.
Please put test files near the main files that you are testing and give them the same name. If you want to test “Auth.js”, please create a file named “Auth.test.js” and put it in the same folder.
Framework (app) Instance
Of course, inside a test, you need to have access to the framework instance. It will be available via appInstanceHelper
import { appInstance } from "@adaptivestone/framework/helpers/appInstance.js";
Run Tests
npm test
Before Scripts
The test entry point is at the project level in ‘src/test/setupVite.js’. This file prepares all folder configs, requires the framework setup, and prepares the global setup for tests.
The minimum Vite config file should contain:
test: {
globalSetup: [ // This script will start Mongo (DIST in important there)
'node_modules/@adaptivestone/framework/dist/tests/globalSetupVitest',
],
setupFiles: [
'./src/tests/setup.ts', // this is a config files with directory location
'@adaptivestone/framework/tests/setupVitest', // This is the entry point for testing from the framework
'./src/tests/setupHook.ts', // This is a local config file (see below)
],
}
Global setup (once per test running)
You are able to provide additional setup and teardown functions to global setup. Just add your implementation in an additional (or instead of) globalSetup
test: {
globalSetup: [
'node_modules/@adaptivestone/framework/dist/tests/globalSetupVitest',
'./src/tests/globalSetup.ts', // custom file for global setup and teardown
],
// ...
}
Custom hooks (beforeAll, etc) per test
Testing helpers provide isolation of modules, and we run preparation of the framework and teardown for each test as well.
test: {
//...
setupFiles: [
'./src/tests/setup.ts', // this is a config files with directory location
'@adaptivestone/framework/tests/setupVitest', // This is the entry point for testing from the framework
'./src/tests/setupHooks.ts', // <-- we are able to provide custom logic there
],
}
You can provide any amount of testing configs
Example:
import { beforeAll, afterAll, beforeEach, afterEach } from "vitest";
import { createDefaultTestUser } from "./testHelpers.ts";
beforeAll(async () => {
await createDefaultTestUser();
});
afterAll(async () => {
// do something
});
afterAll(async () => {
// do something
});
afterEach(async () => {
// do something
});
Default User for Testing
You are able to call the creation of a default user. The user is not created by default. You should call it manually.
import {
defaultUser, // instance of user if default user was created
defaultAuthToken, // default token for auth if user was created
createDefaultTestUser, // create default user and populate defaultUser and defaultAuthToken.
} from "@adaptivestone/framework/tests/testHelpers.js";
const { user, token } = await createDefaultTestUser();
// defaultUser - same user
// defaultAuthToken - same token
Mongo Instance
As the framework is designed to work with MongoDB and provide easy integration with it, it also comes with MongoDB integration in tests.
The integration is done with the help of the MongoDbMemoryServer package.
By default, the framework starts the Mongo memory server and stops it afterward. So you can use Mongo during your tests.
Mongo Tests on ARM64 Machines (Docker)
For ARM64, we have an interesting situation. Mongo Inc. provides binaries for Ubuntu but not for Debian, but official Node images exist for Debian but not for Ubuntu.
To solve this situation, we provide our own Node Docker image based on Ubuntu. You can find it here: ubuntu-node-docker.
Running Tests in CI (GitLab)
An important thing about testing is that tests should be executed automatically on every Git commit. That is where CI (Continuous Integration) comes in.
A .gitlab-ci.yml
sample is below:
stages:
- install
- checks
install:
stage: install
image: registry.gitlab.com/adaptivestone/ubuntu-node:latest
script:
- node -v
- npm install
artifacts:
paths:
- node_modules/
expire_in: 2 hour
codestyle:
stage: checks
image: registry.gitlab.com/adaptivestone/ubuntu-node:latest
needs:
- install
dependencies:
- install
allow_failure: true
script:
- npm run codestyle
tests:
stage: checks
image: registry.gitlab.com/adaptivestone/ubuntu-node:latest
needs:
- install
dependencies:
- install
script:
- npm run test
Running Tests in CI (GitHub)
It is better to look at the repo.
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Test
on:
push:
branches: ["*"]
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
services:
redis:
image: redis:latest
ports:
- 6379:6379
env:
LOGGER_CONSOLE_LEVEL: "error"
REDIS_URI: redis://localhost
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: "latest"
cache: "npm"
- name: npm clean install
run: npm ci
- name: Run Test
run: npm test
- name: Upload results to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
Server instance access
It's possible that in testing you will need to have low-level access to the server itself. We have a helper there too.
import { serverInstance } from "@adaptivestone/framework/tests/testHelpers.ts";
HTTP Endpoint Testing
The framework provides a special function getTestServerURL
to help you construct a full URL for testing.
import { getTestServerURL } from "@adaptivestone/framework/tests/testHelpers.js";
const url = getTestServerURL("/auth");
Full example:
import {
getTestServerURL,
defaultAuthToken,
} from "@adaptivestone/framework/tests/testHelpers.ts";
describe("module", () => {
describe("function", () => {
it("test", async () => {
expect.assertions(1);
const { status } = await fetch(getTestServerURL("/some/endpoint"), {
method: "POST",
headers: {
"Content-type": "application/json",
Authorization: defaultAuthToken,
},
body: JSON.stringify({
// request object
oneData: 1,
secondDate: 2,
}),
}).catch(() => {});
expect(status).toBe(400);
});
});
});
Test Helpers
The framework provides a set of helpers to simplify testing.
import {
getTestServerURL, // return server URL for testing getTestServerURL('auth');
defaultUser, // instance of user if default user was created
defaultAuthToken, // default token for auth if user was created
serverInstance, // server instance for low level interaction
createDefaultTestUser, // create default user and populate defaultUser and defaultAuthToken.
setDefaultUser, // in case you want to have own user implementation setDefaultUser(yourUser)
setDefaultAuthToken, // in case you want to have own user implementation setDefaultAuthToken(token)
} from "@adaptivestone/framework/tests/testHelpers.js";
Mock
In most cases, your code depends on external services, but you still need to perform testing. Calling an external service for each test can be expensive and is not necessary. For this problem, Vitest provides mock options. This is when, instead of calling the real SDK of a service, you call a fake function that provides the result without API calls.
https://vitest.dev/api/vi.html#vi-mock
Mocking a Function
https://vitest.dev/api/vi.html#mocking-functions-and-objects
You can redefine an import for your own import.
vi.doMock("../file.js", () => ({
fileFunction: async () => ({
isSuccess: true,
}),
}));
Redefine one method in a file:
import S3 from "../S3.js";
vi.spyOn(S3, "validateCreds").mockImplementation(() => true);
There are many more mocking options. Please refer to the Vitest documentation for others.