1.2.0 • Published 11 months ago

@withjoy/server-core-test v1.2.0

Weekly downloads
52
License
MIT
Repository
-
Last release
11 months ago

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 imported or required -- 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
{
  "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 and ts-jest are the usual suspects
    • server-core-test is built to work with the framework versions in its own devDependencies
{
  "name": "my_service",
  "devDependencies": {
    ...
    "@types/jest": "___",
    "jest": "___",
    "ts-jest": "___",
    ...
  }
  • add the Standard test:* Tasks to your package.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
NamePurpose
JEST_CANARY_SPECa 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_HOSTPostgres / 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 it
    • setupFilesAfterEnv 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
  • 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 a test:* 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)

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:

NamePurpose
test:builduse your Test Suite's tsconfig file to compile and help identify TypeScript issues, etc.
test:databasedrop and reconstruct your Test Suite database with the latest typeorm schema
test:debugrun your Test Suite with the Node 'inspect' debugging client
test:jestrecompile your Test Suite in jest (so that it won't timeout the first Test Case)
test:onlyrun specific Test Cases without a Coverage summary (plus other refinements of jest)
test:replrun your Test Suite within the Node repl environment
test:setuprun 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)
  • 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 Case
  • const 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
1.2.0

11 months ago

1.1.0

1 year ago

1.1.0-beta

1 year ago

1.0.0

2 years ago

1.0.0-beta.0

2 years ago

0.6.2

2 years ago

0.7.0

2 years ago

0.6.1

2 years ago

0.6.0

2 years ago

0.4.3-a

2 years ago

0.4.1-a

2 years ago

0.4.3-b

2 years ago

0.5.3

2 years ago

0.5.0

2 years ago

0.4.1

2 years ago

0.4.0

2 years ago

0.5.2

2 years ago

0.4.3

2 years ago

0.5.1

2 years ago

0.4.2

2 years ago

0.3.0

3 years ago

0.2.2

3 years ago

0.2.1

3 years ago

0.2.0

3 years ago

0.1.3

3 years ago

0.1.2

3 years ago

0.1.1

3 years ago

0.1.0

3 years ago

0.0.8

3 years ago

0.0.7

3 years ago

0.0.6

3 years ago

0.0.5

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago