@withjoy/server-core-test v1.2.0
server-core-test
Test Suite support for Gateway Services and other Server components
Getting Started
You do not need any external components / dependencies to manage this module.
# set up Node
nvm use
yarn install
# run the Test Suite
yarn test:setup # once only, then ...
yarn test
All shell tasks for the Service are executed with scripts from package.json
.
Here are some of the basics:
# compile TypeScript => `dist/*`
yarn build
yarn test:build
# run the Test Suite
yarn test
yarn test:only test/examples/canary.ts
Upgrading @withjoy/server-core
in this repo's 'package.json'
Because of complex dependences, resolved by
{ "dependencies": { "@withjoy/server-core": "*" } }
in this module{ "dependencies": { "@withjoy/server-core": "*" } }
+{ "resolutions": { "@withjoy/server-core": "^#.##.#" } }
in the importing Service
... we avoid TypeScript hell, like
error TS2344: Type 'EventContext' does not satisfy the constraint 'Context'.
Types have separate declarations of a private property '_userId'.
However (last thing I knew), we can't specify { "resolutions" }
locally because it mucks up everything again.
Instead, we have to toss our lock file every time we need to upgrade.
# wait for it to publish
yarn info --json @withjoy/server-core | jq -r '.data.version'
# full relock
rm yarn.lock && yarn install
Using this Module
This module introduces a lot of Features -- they are covered below.
This module also represents a baseline for shared Test Suite tooling. The modules in this repo are intended to work as a unified set.
We've had various testing library modules conflict with each other;
the set of libraries in this module's 'package.json' should all play together nicely.
Hence the recommendation about peerDependencies
below.
Sometimes the library versions are also coupled to the test framework (jest
) version.
Several of these framework dependencies are "runtime-only" -- they are never import
ed or require
d --
which makes it challenging for yarn
to infer the 'correct' version for "*".
That is why peerDependencies
wildcards are not the correct strategy for test framework modules.
Each version of server-core-test
is built to work with the specific set of framework versions in its own devDependencies
.
Call out those same frameworks and versions in your project's devDependencies
.
Best Practices for Adopting this Module
- install
@withjoy/server-core-test
in your Service with an equal or later version of its@withjoy/server-core
dependency
{
"name": "my_service",
"resolutions": {
"@withjoy/server-core": "^1.2.3"
},
"dependencies": {
"@withjoy/server-core": "*"
},
"devDependencies": {
"@withjoy/server-core-test": "~0.1.0"
}
}
- use its toolkits as
peerDependencies
when possible- eg.
nock
,node-mocks-http
, and their matching@types/*
module
- eg.
{
"name": "my_service",
"peerDependencies": {
...
"@types/jest": "*",
"@types/mock-fs": "*",
"@types/sinon": "*",
"@types/supertest": "*",
"jest": "*",
"mock-fs": "*",
"nock": "*",
"node-mocks-http": "*",
"sinon": "*",
"supertest": "*",
"ts-jest": "*",
...
},
}
- call out specific test framework versions in
devDependencies
jest
andts-jest
are the usual suspectsserver-core-test
is built to work with the framework versions in its owndevDependencies
{
"name": "my_service",
"devDependencies": {
...
"@types/jest": "___",
"jest": "___",
"ts-jest": "___",
...
}
- add the Standard
test:*
Tasks to yourpackage.json
- (and always run them from the root of your repo -- there is path-related weirdness)
- @see the Features documentation below
- the
"..."
is a bit custom per repo -- it's best to copy-and-refine these Tasks from a similar repo's 'package.json'
{
"name": "my_service",
"scripts": {
"test:build": "...",
"test:database": "...",
"test:debug": "...",
"test:jest": "...",
"test:only": "...",
"test:repl": "...",
"test:setup": "yarn test:jest",
"test": "..."
},
}
- create a
test/.env
file in your repo with the following minimum configuration
Name | Purpose |
---|---|
JEST_CANARY_SPEC | a repo-relative reference to any of its Test Suites, big or small, for use by test:jest |
... and if your repo has a Postgres database ... | |
POSTGRES_HOST | Postgres / typeorm configuration referencing a unique database for your Test Suite |
POSTGRES_PORT | ... |
POSTGRES_DATABASE | ... |
POSTGRES_USER | ... |
POSTGRES_PASSWORD | (non-blank value) |
- integrate the module into your
jest
configuration /jest.config.js
globalSetup
should reference '@withjoy/server-core-test/dist/globalSetup/index.js', or a local JavaScript file which includes & leverages itsetupFilesAfterEnv
should reference a local TypeScript file which sets up your Service
module.exports = {
globalSetup: '<rootDir>/node_modules/@withjoy/server-core-test/dist/globalSetup/index.js',
setupFilesAfterEnv: [
'<rootDir>/test/helpers/setupFilesAfterEnv.ts',
],
};
- add custom integration points for your Service
- a
setupFilesAfterEnv
file (see above), which configures the Database Lifecycle (see below) - a
fakeComponents
file, to provide Fake Components (see below) - @see the Features documentation below
- it's best to copy-and-refine these files from a similar repo
- a
- provide CircleCI config & setup
- the details of setting up a Postgres sidecar (etc.) can be derived from the working example of another Service.
- if your Workflow uses an Alpine container, make sure to install
bash
in any Step which runs atest:*
task
apk update && apk add bash
Example Module Adoption PRs
Here are some example PRs which demonstrate the steps required for integrating this module into a Service:
for version 0.1.x (the initial adoption)
- backend-seed
- event_service
- identity_service, specifically this commit
then, later on,
- media_service, a new introduction with little back-correction needed
- product_service, with a significant amount of in-place changes to meet 'server-core-test' conventions
- email_service, a massive upgrade which integrates this module, and applies other repo Best Practices du jour
Features
Standard test:*
Tasks
Add the following to the 'scripts' of your package.json
:
Name | Purpose |
---|---|
test:build | use your Test Suite's tsconfig file to compile and help identify TypeScript issues, etc. |
test:database | drop and reconstruct your Test Suite database with the latest typeorm schema |
test:debug | run your Test Suite with the Node 'inspect' debugging client |
test:jest | recompile your Test Suite in jest (so that it won't timeout the first Test Case) |
test:only | run specific Test Cases without a Coverage summary (plus other refinements of jest ) |
test:repl | run your Test Suite within the Node repl environment |
test:setup | run test:database + test:jest in sequence to prepare a clean environment |
If you have set up a custom Postgres superuser, you may specify it via the environment:
POSTGRES_SUPERUSER=root yarn test:setup
POSTGRES_SUPERUSER=root yarn test:database
setupFilesAfterEnv
File
This is a custom file, implemented by your Service, which makes use of jest's Lifecycle.
It leverages the setupAfterEnv
method in our module to provide setup & teardown of
- the Database Lifecycle (see below)
nock
configuration, which ensures that your Test Suite will never make an external HTTP request
The setupAfterEnv
helper is not a named export; you'll need to import it from 'dist/*'.
The reasons are explained in other places of this codebase.
import { setupAfterEnv } from '@withjoy/server-core-test/dist/setupFilesAfterEnv/index';
Database Lifecycle
We've found its a good practice to have a real Postgres database backing our Test Suites for use by typeorm
.
It take a bit more time to run, and requires single-threaded Test execution (to provide exclusive access to the database),
but significantly reduces the need for mocking and emulates real-world use cases RE: foreign keys, index collisions, etc.
In your Service's setupFilesAfterEnv
file, use setupAfterEnv({ database })
to provide:
- a full count of all Fixtures saved to in the database, which (when 0) tells the Lifecycle that the database instance is empty and safe to use
- any "seeding" logic which saves global Fixtures to the database
- "weeding" logic, which deletes all Fixtures from the database, resulting in a Fixture count of 0
Fake Components
A "Fake Component" is a Class instance used by your Test Suite and associated with your Service's Context
implementation.
Your Service is expected to implement:
- a
Context
Class- extending
@withjoy/server-core
's base Class - whose constructor accepts standard and Service-specific arguments (as separate parameters)
- extending
- a TypeScript definition for the Service-specific Context arguments
- a Context constructor Function
- a Context "injection handler" Function, which returns an
express
middleware Function that binds a Request and a Context
There are more details beyond the scope of this README. Suffice is to say; there are opinionated conventions.
Leveraging your Service's Context tooling, create a custom fakeComponents
file which:
- implements a
FakeComponentProvider
singleton, which (at a minimum) can construct a Context and an ApolloServer - exports the singleton's
testSetup*
methods for use by your Test Suite
Fixture Factories
A "Fixture" is a populated Model, ready to be saved to the Test Suite database.
We have extended the rosie module with some typeorm
tooling to suit our purposes.
There is one "Factory" per Model, and each should return an instance of the Model (a Fixture) with all required fields (etc.) satisfied; the caller should be able to save the resulting Fixture to the database without specifying any custom arguments.
There are two core methods on a Factory;
build
can be used to produce a Fixture which is used within the scope of one Test Caseconst
must be used for any shared Fixture, one that is declared outside the scope of a specific Test Case
typeorm
will mutate a Model in-place, so re-saving a shared Fixture across more than one Test Case will cause problems.
The purpose of Factory#const
is to freeze the populuated Model so that it cannot be saved by accident.
The 'clone'-related methods on a Factory are intended for use with const
Fixtures.
Fake Config
Normally, the Service-wide configuration is a shared mutable singleton Object initially read from test/.env
.
There are times it is useful to mutate the configuration to meet the needs of a Test Case.
You may implement a FakeServerConfigProvider
which can mutate your singleton config Object in-place,
with the ability to safely restore back to its original state when your Test Case is done.
Much like with Fake Components,
- implement a
FakeServerConfigProvider
singleton constructed around your singleton config Object - exports the singleton's
*FakeServerConfig
methods for use by your Test Suite
Publishing
To publish a new version of this module,
- do not up-version on your development branch
- merge your fixes into
master
- from the
master
branch,
yarn version --patch # or whatever is suitable
As a follow-up,
package.json
is up-versioned- a semver-ish tag is pushed to Git
- CircleCI will perform the
yarn publish
operation when it detects the tag - it's ready once the 'versions' in
yarn info @withjoy/server-core
have been updated
CircleCI
Its Project uses the following Environment Variables:
- ARTIFACTORY_TOKEN
- NPM_TOKEN
11 months ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago