Tests are written in vitest
Test initialization goes like this:
-
api/vitest.config.mtsloads the ts config and finds the appropriate setup functions. -
Before running the tests, it runs the
globalSetupfunction fromapi/tests/global-setup.ts. Things like setting up the database and running migrations and base seeds. -
Next it loads a specific test file triggers the
setupFilesfiles, currently onlyapi/tests/setup.ts. These setup files add callbacks that will run before/after each test file runs, so they should be performant. Mostly cleanup functions. -
It runs the actual tests in the loaded file.
-
(Currently) Runs
beforeEachcallback that cleans the database before each test file is run. -
Runs the next test file, and repeats from step 3.
-
Tests should map to a specific file in the api/src folder.
e.g.
api/src/models/funding-submission-line-json.tsmaps toapi/tests/models/funding-submission-line-json.test.tsapi/src/services/centre-services.tsmaps toapi/tests/services/centre-services.test.ts
-
Tests should follow the naming convention
{filename}.test.{extension}. -
Test file location should be moved if a given file is moved, and deleted if the file under test is deleted.
-
A good general pattern for a test is
describe("api/src/services/centre-services.ts", () => { // references file under test describe("CentreServices", () => { // references class or model under test describe(".create", () => { // referneces a specific method on the class or model test("creates a new centre in the database", async () => { // descriptive message about the specific behaviour under test }) }) })
-
In isolated model or scope tests, avoid redundant
whereclauses that only restate the records created in the test. Only include extra filters when that filter is part of the behavior under test. -
Prefer one
expect(...)per test. If you need to verify multiple outcomes, split them into separate tests with narrow assertions. -
Prefer concrete record assertions over count-only assertions. When asserting persisted results, prefer
findAll()on the full table and compare the returned records directly. Do not add restrictivewhereclauses ororderclauses unless that filter or ordering is part of the behavior under test. -
Use full, descriptive variable names in tests. Avoid abbreviations like
persistedCategorywhenpersistedBuildingExpenseCategoryis clearer. -
Order test imports by conceptual distance: third-party libraries first, then local project imports such as models, then factories, and finally the file under test.
-
I'm using a plugin that lets me switch between the test and non-test file, and creates the test file if it does not exist. It's not great, but it mostly works. See https://marketplace.visualstudio.com/items?itemName=klondikemarlen.create-test-file
It requires this config (in your workspace or .vscode/settings.json).
Note that if this is in your worspace config must be inside the "settings" entry. i.e.
{ "settings": { // these settings } }.
{
"createTestFile.nameTemplate": "{filename}.test.{extension}",
"createTestFile.languages": {
"[vue]": {
"createTestFile.nameTemplate": "{filename}.test.{extension}.ts"
}
},
"createTestFile.pathMaps": [
{
// Other examples
// "pathPattern": "/?(.*)",
// "testFilePathPattern": "spec/$1"
"pathPattern": "(api)/src/?(.*)",
"testFilePathPattern": "$1/tests/$2"
},
{
"pathPattern": "(web)/src/?(.*)",
"testFilePathPattern": "$1/tests/$2"
}
],
"createTestFile.isTestFileMatchers": [
"^(?:test|spec)s?/",
"/(?:test|spec)s?/",
"/?(?:test|spec)s?_",
"/?_(?:test|spec)s?",
"/?\\.(?:test|spec)s?",
"/?(?:test|spec)s?\\."
]
}