Skip to content

Commit 0414730

Browse files
committed
reorganize and fix tests
1 parent eaac1f5 commit 0414730

File tree

2 files changed

+161
-120
lines changed

2 files changed

+161
-120
lines changed

packages/nextjs/test/index.client.test.ts

Lines changed: 86 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getGlobalObject, logger } from '@sentry/utils';
77
import { JSDOM } from 'jsdom';
88

99
import { init, Integrations, nextRouterInstrumentation } from '../src/index.client';
10-
import { NextjsOptions } from '../src/utils/nextjsOptions';
10+
import { UserIntegrationsFunction } from '../src/utils/userIntegrations';
1111

1212
const { BrowserTracing } = TracingIntegrations;
1313

@@ -32,6 +32,10 @@ afterAll(() => {
3232
Object.defineProperty(global, 'location', { value: originalGlobalLocation });
3333
});
3434

35+
function findIntegrationByName(integrations: Integration[] = [], name: string): Integration | undefined {
36+
return integrations.find(integration => integration.name === name);
37+
}
38+
3539
describe('Client init()', () => {
3640
afterEach(() => {
3741
jest.clearAllMocks();
@@ -95,84 +99,101 @@ describe('Client init()', () => {
9599
});
96100

97101
describe('integrations', () => {
98-
it('does not add BrowserTracing integration by default if tracesSampleRate is not set', () => {
99-
init({});
100-
101-
const reactInitOptions: NextjsOptions = reactInit.mock.calls[0][0];
102-
expect(reactInitOptions.integrations).toBeUndefined();
103-
});
102+
// Options passed by `@sentry/nextjs`'s `init` to `@sentry/react`'s `init` after modifying them
103+
type ModifiedInitOptionsIntegrationArray = { integrations: Integration[] };
104+
type ModifiedInitOptionsIntegrationFunction = { integrations: UserIntegrationsFunction };
104105

105-
it('adds BrowserTracing integration by default if tracesSampleRate is set', () => {
106-
init({ tracesSampleRate: 1.0 });
106+
it('supports passing unrelated integrations through options', () => {
107+
init({ integrations: [new Integrations.Breadcrumbs({ console: false })] });
107108

108-
const reactInitOptions: NextjsOptions = reactInit.mock.calls[0][0];
109-
expect(reactInitOptions.integrations).toHaveLength(1);
109+
const reactInitOptions = reactInit.mock.calls[0][0] as ModifiedInitOptionsIntegrationArray;
110+
const breadcrumbsIntegration = findIntegrationByName(reactInitOptions.integrations, 'Breadcrumbs');
110111

111-
const integrations = reactInitOptions.integrations as Integration[];
112-
expect(integrations[0]).toEqual(expect.any(BrowserTracing));
113-
// eslint-disable-next-line @typescript-eslint/unbound-method
114-
expect((integrations[0] as InstanceType<typeof BrowserTracing>).options.routingInstrumentation).toEqual(
115-
nextRouterInstrumentation,
116-
);
112+
expect(breadcrumbsIntegration).toBeDefined();
117113
});
118114

119-
it('adds BrowserTracing integration by default if tracesSampler is set', () => {
120-
init({ tracesSampler: () => true });
115+
describe('`BrowserTracing` integration', () => {
116+
it('adds `BrowserTracing` integration if `tracesSampleRate` is set', () => {
117+
init({ tracesSampleRate: 1.0 });
118+
119+
const reactInitOptions = reactInit.mock.calls[0][0] as ModifiedInitOptionsIntegrationArray;
120+
const browserTracingIntegration = findIntegrationByName(reactInitOptions.integrations, 'BrowserTracing');
121+
122+
expect(browserTracingIntegration).toBeDefined();
123+
expect(browserTracingIntegration).toEqual(
124+
expect.objectContaining({
125+
options: expect.objectContaining({
126+
routingInstrumentation: nextRouterInstrumentation,
127+
}),
128+
}),
129+
);
130+
});
121131

122-
const reactInitOptions: NextjsOptions = reactInit.mock.calls[0][0];
123-
expect(reactInitOptions.integrations).toHaveLength(1);
132+
it('adds `BrowserTracing` integration if `tracesSampler` is set', () => {
133+
init({ tracesSampler: () => true });
124134

125-
const integrations = reactInitOptions.integrations as Integration[];
126-
expect(integrations[0]).toEqual(expect.any(BrowserTracing));
127-
// eslint-disable-next-line @typescript-eslint/unbound-method
128-
expect((integrations[0] as InstanceType<typeof BrowserTracing>).options.routingInstrumentation).toEqual(
129-
nextRouterInstrumentation,
130-
);
131-
});
135+
const reactInitOptions = reactInit.mock.calls[0][0] as ModifiedInitOptionsIntegrationArray;
136+
const browserTracingIntegration = findIntegrationByName(reactInitOptions.integrations, 'BrowserTracing');
132137

133-
it('supports passing integration through options', () => {
134-
init({ tracesSampleRate: 1.0, integrations: [new Integrations.Breadcrumbs({ console: false })] });
135-
const reactInitOptions: NextjsOptions = reactInit.mock.calls[0][0];
136-
expect(reactInitOptions.integrations).toHaveLength(2);
138+
expect(browserTracingIntegration).toBeDefined();
139+
expect(browserTracingIntegration).toEqual(
140+
expect.objectContaining({
141+
options: expect.objectContaining({
142+
routingInstrumentation: nextRouterInstrumentation,
143+
}),
144+
}),
145+
);
146+
});
137147

138-
const integrations = reactInitOptions.integrations as Integration[];
139-
expect(integrations).toEqual([expect.any(Integrations.Breadcrumbs), expect.any(BrowserTracing)]);
140-
});
148+
it('does not add `BrowserTracing` integration if tracing not enabled in SDK', () => {
149+
init({});
141150

142-
it('uses custom BrowserTracing with array option with nextRouterInstrumentation', () => {
143-
init({
144-
tracesSampleRate: 1.0,
145-
integrations: [new BrowserTracing({ idleTimeout: 5000, startTransactionOnLocationChange: false })],
146-
});
151+
const reactInitOptions = reactInit.mock.calls[0][0] as ModifiedInitOptionsIntegrationArray;
152+
const browserTracingIntegration = findIntegrationByName(reactInitOptions.integrations, 'BrowserTracing');
147153

148-
const reactInitOptions: NextjsOptions = reactInit.mock.calls[0][0];
149-
expect(reactInitOptions.integrations).toHaveLength(1);
150-
const integrations = reactInitOptions.integrations as Integration[];
151-
expect((integrations[0] as InstanceType<typeof BrowserTracing>).options).toEqual(
152-
expect.objectContaining({
153-
idleTimeout: 5000,
154-
startTransactionOnLocationChange: false,
155-
routingInstrumentation: nextRouterInstrumentation,
156-
}),
157-
);
158-
});
154+
expect(browserTracingIntegration).toBeUndefined();
155+
});
159156

160-
it('uses custom BrowserTracing with function option with nextRouterInstrumentation', () => {
161-
init({
162-
tracesSampleRate: 1.0,
163-
integrations: () => [new BrowserTracing({ idleTimeout: 5000, startTransactionOnLocationChange: false })],
157+
it('forces correct router instrumentation if user provides `BrowserTracing` in an array', () => {
158+
init({
159+
tracesSampleRate: 1.0,
160+
integrations: [new BrowserTracing({ startTransactionOnLocationChange: false })],
161+
});
162+
163+
const reactInitOptions = reactInit.mock.calls[0][0] as ModifiedInitOptionsIntegrationArray;
164+
const browserTracingIntegration = findIntegrationByName(reactInitOptions.integrations, 'BrowserTracing');
165+
166+
expect(browserTracingIntegration).toEqual(
167+
expect.objectContaining({
168+
options: expect.objectContaining({
169+
routingInstrumentation: nextRouterInstrumentation,
170+
// This proves it's still the user's copy
171+
startTransactionOnLocationChange: false,
172+
}),
173+
}),
174+
);
164175
});
165176

166-
const reactInitOptions: NextjsOptions = reactInit.mock.calls[0][0];
167-
const integrationFunc = reactInitOptions.integrations as () => Integration[];
168-
const integrations = integrationFunc();
169-
expect((integrations[0] as InstanceType<typeof BrowserTracing>).options).toEqual(
170-
expect.objectContaining({
171-
idleTimeout: 5000,
172-
startTransactionOnLocationChange: false,
173-
routingInstrumentation: nextRouterInstrumentation,
174-
}),
175-
);
177+
it('forces correct router instrumentation if user provides `BrowserTracing` in a function', () => {
178+
init({
179+
tracesSampleRate: 1.0,
180+
integrations: defaults => [...defaults, new BrowserTracing({ startTransactionOnLocationChange: false })],
181+
});
182+
183+
const reactInitOptions = reactInit.mock.calls[0][0] as ModifiedInitOptionsIntegrationFunction;
184+
const materializedIntegrations = reactInitOptions.integrations(SentryReact.defaultIntegrations);
185+
const browserTracingIntegration = findIntegrationByName(materializedIntegrations, 'BrowserTracing');
186+
187+
expect(browserTracingIntegration).toEqual(
188+
expect.objectContaining({
189+
options: expect.objectContaining({
190+
routingInstrumentation: nextRouterInstrumentation,
191+
// This proves it's still the user's copy
192+
startTransactionOnLocationChange: false,
193+
}),
194+
}),
195+
);
196+
});
176197
});
177198
});
178199
});

packages/nextjs/test/index.server.test.ts

Lines changed: 75 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { RewriteFrames } from '@sentry/integrations';
21
import * as SentryNode from '@sentry/node';
32
import { getCurrentHub, NodeClient } from '@sentry/node';
43
import { Integration } from '@sentry/types';
54
import { getGlobalObject, logger } from '@sentry/utils';
65
import * as domain from 'domain';
76

87
import { init } from '../src/index.server';
9-
import { NextjsOptions } from '../src/utils/nextjsOptions';
108

119
const { Integrations } = SentryNode;
1210

@@ -18,6 +16,10 @@ const global = getGlobalObject();
1816
const nodeInit = jest.spyOn(SentryNode, 'init');
1917
const loggerLogSpy = jest.spyOn(logger, 'log');
2018

19+
function findIntegrationByName(integrations: Integration[] = [], name: string): Integration | undefined {
20+
return integrations.find(integration => integration.name === name);
21+
}
22+
2123
describe('Server init()', () => {
2224
afterEach(() => {
2325
jest.clearAllMocks();
@@ -49,7 +51,14 @@ describe('Server init()', () => {
4951
},
5052
autoSessionTracking: false,
5153
environment: 'test',
52-
integrations: [expect.any(RewriteFrames)],
54+
55+
// Integrations are tested separately, and we can't be more specific here without depending on the order in
56+
// which integrations appear in the array, which we can't guarantee.
57+
//
58+
// TODO: If we upgrde to Jest 28+, we can follow Jest's example matcher and create an
59+
// `expect.ArrayContainingInAnyOrder`. See
60+
// https://github.com/facebook/jest/blob/main/examples/expect-extend/toBeWithinRange.ts.
61+
integrations: expect.any(Array),
5362
}),
5463
);
5564
});
@@ -133,82 +142,93 @@ describe('Server init()', () => {
133142
});
134143

135144
describe('integrations', () => {
136-
it('adds RewriteFrames integration by default', () => {
145+
// Options passed by `@sentry/nextjs`'s `init` to `@sentry/node`'s `init` after modifying them
146+
type ModifiedInitOptions = { integrations: Integration[] };
147+
148+
it('adds default integrations', () => {
137149
init({});
138150

139-
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
140-
expect(nodeInitOptions.integrations).toHaveLength(1);
141-
const integrations = nodeInitOptions.integrations as Integration[];
142-
expect(integrations[0]).toEqual(expect.any(RewriteFrames));
151+
const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions;
152+
const rewriteFramesIntegration = findIntegrationByName(nodeInitOptions.integrations, 'RewriteFrames');
153+
154+
expect(rewriteFramesIntegration).toBeDefined();
143155
});
144156

145-
it('adds Http integration by default if tracesSampleRate is set', () => {
146-
init({ tracesSampleRate: 1.0 });
157+
it('supports passing unrelated integrations through options', () => {
158+
init({ integrations: [new Integrations.Console()] });
159+
160+
const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions;
161+
const consoleIntegration = findIntegrationByName(nodeInitOptions.integrations, 'Console');
147162

148-
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
149-
expect(nodeInitOptions.integrations).toHaveLength(2);
150-
const integrations = nodeInitOptions.integrations as Integration[];
151-
expect(integrations[1]).toEqual(expect.any(Integrations.Http));
163+
expect(consoleIntegration).toBeDefined();
152164
});
153165

154-
it('adds Http integration by default if tracesSampler is set', () => {
155-
init({ tracesSampler: () => true });
166+
describe('`Http` integration', () => {
167+
it('adds `Http` integration with tracing enabled if `tracesSampleRate` is set', () => {
168+
init({ tracesSampleRate: 1.0 });
156169

157-
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
158-
expect(nodeInitOptions.integrations).toHaveLength(2);
159-
const integrations = nodeInitOptions.integrations as Integration[];
160-
expect(integrations[1]).toEqual(expect.any(Integrations.Http));
161-
});
170+
const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions;
171+
const httpIntegration = findIntegrationByName(nodeInitOptions.integrations, 'Http');
162172

163-
it('adds Http integration with tracing true', () => {
164-
init({ tracesSampleRate: 1.0 });
165-
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
166-
expect(nodeInitOptions.integrations).toHaveLength(2);
173+
expect(httpIntegration).toBeDefined();
174+
expect(httpIntegration).toEqual(expect.objectContaining({ _tracing: true }));
175+
});
167176

168-
const integrations = nodeInitOptions.integrations as Integration[];
169-
expect((integrations[1] as any)._tracing).toBe(true);
170-
});
177+
it('adds `Http` integration with tracing enabled if `tracesSampler` is set', () => {
178+
init({ tracesSampler: () => true });
171179

172-
it('supports passing integration through options', () => {
173-
init({ tracesSampleRate: 1.0, integrations: [new Integrations.Console()] });
174-
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
175-
expect(nodeInitOptions.integrations).toHaveLength(3);
176-
177-
const integrations = nodeInitOptions.integrations as Integration[];
178-
expect(integrations).toEqual([
179-
expect.any(Integrations.Console),
180-
expect.any(RewriteFrames),
181-
expect.any(Integrations.Http),
182-
]);
183-
});
180+
const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions;
181+
const httpIntegration = findIntegrationByName(nodeInitOptions.integrations, 'Http');
182+
183+
expect(httpIntegration).toBeDefined();
184+
expect(httpIntegration).toEqual(expect.objectContaining({ _tracing: true }));
185+
});
186+
187+
it('does not add `Http` integration if tracing not enabled in SDK', () => {
188+
init({});
184189

185-
describe('custom Http integration', () => {
186-
it('sets tracing to true if tracesSampleRate is set', () => {
190+
const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions;
191+
const httpIntegration = findIntegrationByName(nodeInitOptions.integrations, 'Http');
192+
193+
expect(httpIntegration).toBeUndefined();
194+
});
195+
196+
it('forces `_tracing = true` if `tracesSampleRate` is set', () => {
187197
init({
188198
tracesSampleRate: 1.0,
189199
integrations: [new Integrations.Http({ tracing: false })],
190200
});
191201

192-
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
193-
expect(nodeInitOptions.integrations).toHaveLength(2);
194-
const integrations = nodeInitOptions.integrations as Integration[];
195-
expect(integrations[0] as InstanceType<typeof Integrations.Http>).toEqual(
196-
expect.objectContaining({ _breadcrumbs: true, _tracing: true, name: 'Http' }),
197-
);
202+
const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions;
203+
const httpIntegration = findIntegrationByName(nodeInitOptions.integrations, 'Http');
204+
205+
expect(httpIntegration).toBeDefined();
206+
expect(httpIntegration).toEqual(expect.objectContaining({ _tracing: true }));
198207
});
199208

200-
it('sets tracing to true if tracesSampler is set', () => {
209+
it('forces `_tracing = true` if `tracesSampler` is set', () => {
201210
init({
202211
tracesSampler: () => true,
203212
integrations: [new Integrations.Http({ tracing: false })],
204213
});
205214

206-
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
207-
expect(nodeInitOptions.integrations).toHaveLength(2);
208-
const integrations = nodeInitOptions.integrations as Integration[];
209-
expect(integrations[0] as InstanceType<typeof Integrations.Http>).toEqual(
210-
expect.objectContaining({ _breadcrumbs: true, _tracing: true, name: 'Http' }),
211-
);
215+
const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions;
216+
const httpIntegration = findIntegrationByName(nodeInitOptions.integrations, 'Http');
217+
218+
expect(httpIntegration).toBeDefined();
219+
expect(httpIntegration).toEqual(expect.objectContaining({ _tracing: true }));
220+
});
221+
222+
it('does not force `_tracing = true` if tracing not enabled in SDK', () => {
223+
init({
224+
integrations: [new Integrations.Http({ tracing: false })],
225+
});
226+
227+
const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions;
228+
const httpIntegration = findIntegrationByName(nodeInitOptions.integrations, 'Http');
229+
230+
expect(httpIntegration).toBeDefined();
231+
expect(httpIntegration).toEqual(expect.objectContaining({ _tracing: false }));
212232
});
213233
});
214234
});

0 commit comments

Comments
 (0)