Skip to content

test(node): Add Node SDK integration test structure. #4694

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 19 commits into from
Mar 22, 2022

Conversation

onurtemizkan
Copy link
Collaborator

@onurtemizkan onurtemizkan commented Mar 8, 2022

This PR introduces a structure for integration testing Node SDK,

Summary:

  • Pluggable init, subject and server scripts are put together, to start a test scenario. My first attempt was to bundle them with Webpack and run on a separate thread, but it's not compatible with how nock works (requires to patch and listen on the same thread).
  • The request bodies are matched against jest snapshots. Random or platform-specific properties are patched with expect.any(...). I'm still not sure about this approach as it can be too fragile with even small updates on SDK (especially stack-trace snapshots).
  • cjs/esm builds are separately tested, but we transform esm builds to cjs beforehand with babel, as jest still doesn't support esm modules natively.
  • Having a configurable server script per test will probably make it easier to run and test with databases, but I haven't tried that yet. It would be great to collect some ideas about that as well (My first idea was to have a set of Docker nodes with predefined ports, that we start running before all tests).

This implementation is very basic and there might be many improvements required when we actually start writing tests. It would be great if I can get feedback and reviews about the approach taken here, so we can build the rest on top of that.

cc: @AbhiPrasad, @lobsterkatie

Fixes: #4260

@github-actions
Copy link
Contributor

github-actions bot commented Mar 8, 2022

size-limit report

Path Base Size (a929462) Current Size Change
@sentry/browser - ES5 CDN Bundle (gzipped + minified) 19.49 KB 19.49 KB 0%
@sentry/browser - ES5 CDN Bundle (minified) 62.17 KB 62.17 KB 0%
@sentry/browser - ES6 CDN Bundle (gzipped + minified) 18.12 KB 18.11 KB -0.02% 🔽
@sentry/browser - ES6 CDN Bundle (minified) 55.5 KB 55.5 KB 0%
@sentry/browser - Webpack (gzipped + minified) 22.6 KB 22.6 KB 0%
@sentry/browser - Webpack (minified) 79.21 KB 79.21 KB 0%
@sentry/react - Webpack (gzipped + minified) 22.63 KB 22.63 KB 0%
@sentry/nextjs Client - Webpack (gzipped + minified) 47.6 KB 47.6 KB 0%
@sentry/browser + @sentry/tracing - ES5 CDN Bundle (gzipped + minified) 25.36 KB 25.36 KB 0%
@sentry/browser + @sentry/tracing - ES6 CDN Bundle (gzipped + minified) 23.72 KB 23.72 KB 0%

@onurtemizkan onurtemizkan force-pushed the onur/node-integration-tests branch 2 times, most recently from 3e4b5dd to fb154c9 Compare March 8, 2022 13:49
@onurtemizkan onurtemizkan marked this pull request as ready for review March 8, 2022 16:03
@smeubank
Copy link
Member

smeubank commented Mar 9, 2022

lookin good 👍 in our daily it came up if you think this would be easily re-used for nextjs?

@smeubank smeubank added this to the Release Stability milestone Mar 9, 2022
Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, could you write a README similar to what you did for the playwright tests here?

It can be very WIP - just so we have some of this in code that we can iterate on (also makes future git blames easier instead of digging through PR descriptions)

More fleshed out review incoming

@onurtemizkan
Copy link
Collaborator Author

if you think this would be easily re-used for nextjs?

I think so, it's very similar to the current next server tests after all.

Hey, could you write a README similar to what you did for the playwright tests here?

Sure, will do today. 👍

@lobsterkatie
Copy link
Member

The request bodies are matched against jest snapshots. Random or platform-specific properties are patched with expect.any(...). I'm still not sure about this approach as it can be too fragile with even small updates on SDK (especially stack-trace snapshots).

I think it's a valid concern. I also have never liked the fact that snapshots force you to assert on everything, when any given test is probably only testing a subset (and it's a lot harder to tell what subset because of all of the noise). So I try to avoid them if at all possible.

cjs/esm builds are separately tested, but we transform esm builds to cjs beforehand with babel, as jest still doesn't support esm modules natively.

As we discussed this morning (and which I'm including here for posterity), IMHO testing against our built code isn't really something we need to worry about on the node side. With the browser SDK, we're spinning up all sorts of variations when we build bundles (including ones which explicitly delete some of the code), and they build into something barely human-readable (making them really hard to sanity-check), so testing against them gives us a sense of security we wouldn’t get otherwise. None of that applies to Node, though, so I think our collective building, reviewing, and maintenance time is better spent elsewhere.

Having a configurable server script per test will probably make it easier to run and test with databases, but I haven't tried that yet. It would be great to collect some ideas about that as well (My first idea was to have a set of Docker nodes with predefined ports, that we start running before all tests).

Also from our conversation this morning, I think it's safe to mock the DB methods, rather than worrying about trying to spin up live databases. The thing we're trying to test is whether or not we start and stop spans when we should, and whether we're collecting the correct metadata along the way, but not what happens in between. (Put another way, we instrument the call to the DB, not the DB query itself, so as long as we test that, we're good.)

Copy link
Member

@lobsterkatie lobsterkatie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good so far!

I wonder, though - is there a way to avoid having the relevant code spread over four separate files (some of which seem pretty boilerplate), with each test having to live in a separate folder? I know that this structure is similar to what we have on the browser side, but I've found with those that I end up doing a whole lot of clicking around just to look at all the pieces of one test. What do you think?

const url = await runServer(__dirname);
const requestBody = await getEventRequest(url);

expect(updateForSnapshot(requestBody)).toMatchSnapshot();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on my comment above re: snapshots, I think it's enough if we assert on the exception, similar to how Kamil did it in the nextjs integration tests here:

expectEvent(requests.events[0], {
exception: {
values: [
{
type: 'Error',
value: 'Sentry Frontend Error',
},
],
},
});

All we really want to prove is that calling captureException results in an error event being sent. The rest (should) be covered by our unit tests.

{
"extends": "../../tsconfig.json",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"allowSyntheticDefaultImports": true,

Per the TS docs, turning on esModuleInterop also automatically turns on allowSyntheticDefaultImports.

"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"moduleResolution": "node",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"moduleResolution": "node",

This is inherited from @sentry-internal/typescript via the repo's root-level tsconfig.

"moduleResolution": "node",

"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"moduleResolution": "node",
"noEmit": false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"noEmit": false,

This should be the tsc default.

"esModuleInterop": true,
"moduleResolution": "node",
"noEmit": false,
"strict": true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"strict": true,

This is also inherited:

"moduleResolution": "node",
"noEmit": false,
"strict": true,
"types": ["jest"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"types": ["jest"]
"types": ["jest", "node"]

tsconfig inheritance is unfortunately not additive, so we have to re-specify node here.

@onurtemizkan onurtemizkan force-pushed the onur/node-integration-tests branch from fce6e20 to 8862a29 Compare March 18, 2022 15:23
release: '1.0',
});

Sentry.captureMessage('Message');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's call this suites/public-api/capture-message/... to better match the playwright tests?

release: '1.0',
});

Sentry.captureException('Captured Error');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's try to match how this is often used by users.

Suggested change
Sentry.captureException('Captured Error');
Sentry.captureException(new Error('Captured Error'));

Comment on lines 28 to 34
const [envelopeHeaderString, itemHeaderString, itemString] = body.split('\n');

return {
envelopeHeader: JSON.parse(envelopeHeaderString),
itemHeader: JSON.parse(itemHeaderString),
item: JSON.parse(itemString),
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is dangerous because it assumes only 1 item, while envelopes can have multiple items. Can we work around this? See:

export function parseEnvelope(env: string): Array<Record<any, any>> {
return env.split('\n').map(e => JSON.parse(e));
}

import { assertSentryTransaction, getEnvelopeRequest, runServer } from '../../../utils';

test('should send a manually started transaction.', async () => {
const url = await runServer(__dirname);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this starts a new server for each test. Could that get expensive? Could we somehow re-use the same server instance?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial idea was to make servers pluggable just like templates in Playwright tests, so we can write tests for specific server configurations. Would that make sense to check the overall performance and revisit this when we're writing a bit more complex tests?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that’s fine with me - we can def come back and explore further!

@onurtemizkan
Copy link
Collaborator Author

I wonder, though - is there a way to avoid having the relevant code spread over four separate files (some of which seem pretty boilerplate), with each test having to live in a separate folder? I know that this structure is similar to what we have on the browser side, but I've found with those that I end up doing a whole lot of clicking around just to look at all the pieces of one test. What do you think?

I think for Node tests, we might not need separate init and subject scripts, so I merged them as scenarios. Also for server scripts, I added a default as it looks like most of tests can just use that simple express server.

Still not looking great as we still have a separate folder for each test / scenario. The reason for that is when I wrote more than one test in a set, Jest stopped logging the pure error source (like TypeError: Cannot read property 'finish' of undefined). Instead, it started logging a generic "test suite timeout" as it became a test group. I'm not sure if there's a workaround for it, though. I can look for alternative ways too.

Copy link
Member

@AbhiPrasad AbhiPrasad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not looking great as we still have a separate folder for each test / scenario

I think this is fine - we can come back and re-evaluate if it's such a pain.

@AbhiPrasad
Copy link
Member

@onurtemizkan mind rebasing? Let's get this initial version in and then we can iterate as we go forward.

CHANGELOG.md Outdated

This release fixes a bug from 6.19.0 causing type import errors in most JS SDKs.

fix(types): Point to type definitions in dist folder [#4745](https://github.com/getsentry/sentry-javascript/pull/4745)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be from a bad merge commit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops yes.

@onurtemizkan onurtemizkan force-pushed the onur/node-integration-tests branch from 89a50de to 49ab765 Compare March 22, 2022 13:53
@@ -0,0 +1,12 @@
import { assertSentryTransaction, getEnvelopeRequest, runServer } from '../../../utils';

test.skip('should send a manually started transaction when @sentry/tracing is imported using namespace import.', async () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping this test for now, we can re-enable it with the PR to fix #4731.

@lobsterkatie
Copy link
Member

Still not looking great as we still have a separate folder for each test / scenario

I think this is fine - we can come back and re-evaluate if it's such a pain.

Taking it down from 4 files to 2 is already def an improvement. My main reasons for asking about this were 1) the test tests the results of whatever happens in the scenario, and it's easier to reason about that cause and effect if you can see them both at once, and 2) (less important) I often find it helpful to just scan down the list of test titles (should xyz when abc) to see what's there, and having one per folder makes that tougher.

I can see why they're currently in two files, though, so jest can run a test file, that test file can call runServer, and runServer can require the scenario file. The only way I could see to get around that (maybe) would be to have runServer take an initSentry function which would live in the test file and do whatever is currently happening in the scenario file. I guess that doesn't account for testing imports, though... IDK if it would be the same to import them in the test file or not. Anyway, I'm fine to merge this as a foundation and tweak it as we go.

@lobsterkatie lobsterkatie merged commit 1379591 into master Mar 22, 2022
@lobsterkatie lobsterkatie deleted the onur/node-integration-tests branch March 22, 2022 23:54
AbhiPrasad pushed a commit that referenced this pull request Mar 23, 2022
As part of #4694, which adds integration tests for node, a change was made to the repo-level `yarn test` command, in order to skip running the new tests. The new version of the command was missing a `--ignore` flag, though, causing the command to error out. This adds the missing flag.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New integration tests for @sentry/node
4 participants