Skip to content

test(node): Document Node integration tests. #4791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions packages/node-integration-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Integration Tests for Sentry Node.JS SDK

## Structure

```
suites/
|---- public-api/
|---- captureMessage/
|---- test.ts [assertions]
|---- scenario.ts [Sentry initialization and test subject]
|---- customTest/
|---- test.ts [assertions]
|---- scenario_1.ts [optional extra test scenario]
|---- scenario_2.ts [optional extra test scenario]
|---- server_with_mongo.ts [optional custom server]
|---- server_with_postgres.ts [optional custom server]
utils/
|---- defaults/
|---- server.ts [default Express server configuration]
```

The tests are grouped by their scopes, such as `public-api` or `tracing`. In every group of tests, there are multiple folders containing test scenarios and assertions.

Tests run on Express servers (a server instance per test). By default, a simple server template inside `utils/defaults/server.ts` is used. Every server instance runs on a different port.

A custom server configuration can be used, supplying a script that exports a valid express server instance as default. `runServer` utility function accepts an optional `serverPath` argument for this purpose.

`scenario.ts` contains the initialization logic and the test subject. By default, `{TEST_DIR}/scenario.ts` is used, but `runServer` also accepts an optional `scenarioPath` argument for non-standard usage.

`test.ts` is required for each test case, and contains the server runner logic, request interceptors for Sentry requests, and assertions. Test server, interceptors and assertions are all run on the same Jest thread.

### Utilities

`utils/` contains helpers and Sentry-specific assertions that can be used in (`test.ts`).

## Running Tests Locally

Tests can be run locally with:

`yarn test`

To run tests with Jest's watch mode:

`yarn test:jest`

To filter tests by their title:

`yarn test -t "set different properties of a scope"`

You can refer to [Jest documentation](https://jestjs.io/docs/cli) for other CLI options.
3 changes: 2 additions & 1 deletion packages/node-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"lint:eslint": "eslint . --cache --cache-location '../../eslintcache/' --format stylish",
"lint:prettier": "prettier --check \"{suites,utils}/**/*.ts\"",
"type-check": "tsc",
"test": "jest --detectOpenHandles --runInBand --forceExit"
"test": "jest --detectOpenHandles --runInBand --forceExit",
"test:watch": "yarn test --watch"
},
"dependencies": {
"express": "^4.17.3",
Expand Down
45 changes: 45 additions & 0 deletions packages/node-integration-tests/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import nock from 'nock';
import * as path from 'path';
import { getPortPromise } from 'portfinder';

/**
* Asserts against a Sentry Event ignoring non-deterministic properties
*
* @param {Record<string, unknown>} actual
* @param {Record<string, unknown>} expected
*/
export const assertSentryEvent = (actual: Record<string, unknown>, expected: Record<string, unknown>): void => {
expect(actual).toMatchObject({
event_id: expect.any(String),
Expand All @@ -13,6 +19,12 @@ export const assertSentryEvent = (actual: Record<string, unknown>, expected: Rec
});
};

/**
* Asserts against a Sentry Transaction ignoring non-deterministic properties
*
* @param {Record<string, unknown>} actual
* @param {Record<string, unknown>} expected
*/
export const assertSentryTransaction = (actual: Record<string, unknown>, expected: Record<string, unknown>): void => {
expect(actual).toMatchObject({
event_id: expect.any(String),
Expand All @@ -24,10 +36,23 @@ export const assertSentryTransaction = (actual: Record<string, unknown>, expecte
});
};

/**
* Parses response body containing an Envelope
*
* @param {string} body
* @return {*} {Array<Record<string, unknown>>}
*/
export const parseEnvelope = (body: string): Array<Record<string, unknown>> => {
return body.split('\n').map(e => JSON.parse(e));
};

/**
* Intercepts and extracts multiple requests containing a Sentry Event
*
* @param {string} url
* @param {number} count
* @return {*} {Promise<Array<Record<string, unknown>>>}
*/
export const getMultipleEventRequests = async (url: string, count: number): Promise<Array<Record<string, unknown>>> => {
const events: Record<string, unknown>[] = [];

Expand All @@ -47,10 +72,22 @@ export const getMultipleEventRequests = async (url: string, count: number): Prom
});
};

/**
* Intercepts and extracts a single request containing a Sentry Event
*
* @param {string} url
* @return {*} {Promise<Record<string, unknown>>}
*/
export const getEventRequest = async (url: string): Promise<Record<string, unknown>> => {
return (await getMultipleEventRequests(url, 1))[0];
};

/**
* Intercepts and extracts a request containing a Sentry Envelope
*
* @param {string} url
* @return {*} {Promise<Array<Record<string, unknown>>>}
*/
export const getEnvelopeRequest = async (url: string): Promise<Array<Record<string, unknown>>> => {
return new Promise(resolve => {
nock('https://dsn.ingest.sentry.io')
Expand All @@ -65,6 +102,14 @@ export const getEnvelopeRequest = async (url: string): Promise<Array<Record<stri
});
};

/**
* Runs a test server
*
* @param {string} testDir
* @param {string} [serverPath]
* @param {string} [scenarioPath]
* @return {*} {Promise<string>}
*/
export async function runServer(testDir: string, serverPath?: string, scenarioPath?: string): Promise<string> {
const port = await getPortPromise();
const url = `http://localhost:${port}/test`;
Expand Down