Skip to content

Commit a929462

Browse files
authored
test(integrations): Add unit tests for CaptureConsole (#4733)
1 parent 29d5a16 commit a929462

File tree

1 file changed

+327
-0
lines changed

1 file changed

+327
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
import { Event, Integration } from '@sentry/types';
2+
3+
import { CaptureConsole } from '../src/captureconsole';
4+
5+
const mockScope = {
6+
setLevel: jest.fn(),
7+
setExtra: jest.fn(),
8+
addEventProcessor: jest.fn(),
9+
};
10+
11+
const mockHub = {
12+
withScope: jest.fn(callback => {
13+
callback(mockScope);
14+
}),
15+
captureMessage: jest.fn(),
16+
captureException: jest.fn(),
17+
};
18+
19+
const getMockHubWithIntegration = (integration: Integration) => ({
20+
...mockHub,
21+
getIntegration: jest.fn(() => integration),
22+
});
23+
24+
// We're using this to un-monkey patch the console after each test.
25+
const originalConsole = Object.assign({}, global.console);
26+
27+
describe('CaptureConsole setup', () => {
28+
afterEach(() => {
29+
jest.clearAllMocks();
30+
31+
// Un-monkey-patch the console functions
32+
Object.assign(global.console, originalConsole);
33+
});
34+
35+
it('should patch user-configured console levels', () => {
36+
const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'warn'] });
37+
captureConsoleIntegration.setupOnce(
38+
() => undefined,
39+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
40+
);
41+
42+
expect(global.console.error).toBe(originalConsole.error); // not monkey patched
43+
expect(global.console.log).not.toBe(originalConsole.log); // monkey patched
44+
expect(global.console.warn).not.toBe(originalConsole.warn); // monkey patched
45+
});
46+
47+
it('should fall back to default console levels if none are provided', () => {
48+
const captureConsoleIntegration = new CaptureConsole();
49+
captureConsoleIntegration.setupOnce(
50+
() => undefined,
51+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
52+
);
53+
54+
// expect a set of defined console levels to have been monkey patched
55+
expect(global.console.debug).not.toBe(originalConsole.debug);
56+
expect(global.console.info).not.toBe(originalConsole.info);
57+
expect(global.console.warn).not.toBe(originalConsole.warn);
58+
expect(global.console.error).not.toBe(originalConsole.error);
59+
expect(global.console.log).not.toBe(originalConsole.log);
60+
expect(global.console.assert).not.toBe(originalConsole.assert);
61+
62+
// any other fields should not have been patched
63+
expect(global.console.trace).toBe(originalConsole.trace);
64+
expect(global.console.table).toBe(originalConsole.table);
65+
});
66+
67+
it('should not wrap any functions with an empty levels option', () => {
68+
const captureConsoleIntegration = new CaptureConsole({ levels: [] });
69+
captureConsoleIntegration.setupOnce(
70+
() => undefined,
71+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
72+
);
73+
74+
// expect the default set of console levels not to have been monkey patched
75+
expect(global.console.debug).toBe(originalConsole.debug);
76+
expect(global.console.info).toBe(originalConsole.info);
77+
expect(global.console.warn).toBe(originalConsole.warn);
78+
expect(global.console.error).toBe(originalConsole.error);
79+
expect(global.console.log).toBe(originalConsole.log);
80+
expect(global.console.assert).toBe(originalConsole.assert);
81+
82+
// expect no message to be captured with console.log
83+
global.console.log('some message');
84+
expect(mockHub.captureMessage).not.toHaveBeenCalled();
85+
});
86+
87+
it('setup should fail gracefully when console is not available', () => {
88+
const consoleRef = global.console;
89+
// remove console
90+
delete global.console;
91+
92+
expect(() => {
93+
const captureConsoleIntegration = new CaptureConsole();
94+
captureConsoleIntegration.setupOnce(
95+
() => undefined,
96+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
97+
);
98+
}).not.toThrow();
99+
100+
// reinstate initial console
101+
global.console = consoleRef;
102+
});
103+
104+
it('should set a level in the scope when console function is called', () => {
105+
const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] });
106+
captureConsoleIntegration.setupOnce(
107+
() => undefined,
108+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
109+
);
110+
111+
// call a wrapped function
112+
global.console.error('some logging message');
113+
114+
expect(mockScope.setLevel).toHaveBeenCalledTimes(1);
115+
expect(mockScope.setLevel).toHaveBeenCalledWith('error');
116+
});
117+
118+
it('should send arguments as extra data on failed assertion', () => {
119+
const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] });
120+
captureConsoleIntegration.setupOnce(
121+
() => undefined,
122+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
123+
);
124+
125+
// call a wrapped function
126+
global.console.log('some arg 1', 'some arg 2');
127+
global.console.log();
128+
129+
expect(mockScope.setExtra).toHaveBeenCalledTimes(2);
130+
expect(mockScope.setExtra).toHaveBeenCalledWith('arguments', ['some arg 1', 'some arg 2']);
131+
expect(mockScope.setExtra).toHaveBeenCalledWith('arguments', []);
132+
});
133+
134+
it('should add an event processor that sets the `logger` field of events', () => {
135+
const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] });
136+
captureConsoleIntegration.setupOnce(
137+
() => undefined,
138+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
139+
);
140+
141+
// call a wrapped function
142+
global.console.log('some message');
143+
144+
expect(mockScope.addEventProcessor).toHaveBeenCalledTimes(1);
145+
146+
const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0][0];
147+
const someEvent: Event = {};
148+
addedEventProcessor(someEvent);
149+
150+
expect(someEvent.logger).toBe('console');
151+
});
152+
153+
it('should capture message on a failed assertion', () => {
154+
const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] });
155+
captureConsoleIntegration.setupOnce(
156+
() => undefined,
157+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
158+
);
159+
160+
global.console.assert(1 + 1 === 3);
161+
162+
expect(mockScope.setExtra).toHaveBeenLastCalledWith('arguments', []);
163+
expect(mockHub.captureMessage).toHaveBeenCalledTimes(1);
164+
expect(mockHub.captureMessage).toHaveBeenCalledWith('Assertion failed: console.assert');
165+
});
166+
167+
it('should capture correct message on a failed assertion with message', () => {
168+
const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] });
169+
captureConsoleIntegration.setupOnce(
170+
() => undefined,
171+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
172+
);
173+
174+
global.console.assert(1 + 1 === 3, 'expression is false');
175+
176+
expect(mockScope.setExtra).toHaveBeenLastCalledWith('arguments', ['expression is false']);
177+
expect(mockHub.captureMessage).toHaveBeenCalledTimes(1);
178+
expect(mockHub.captureMessage).toHaveBeenCalledWith('Assertion failed: expression is false');
179+
});
180+
181+
it('should not capture message on a successful assertion', () => {
182+
const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] });
183+
captureConsoleIntegration.setupOnce(
184+
() => undefined,
185+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
186+
);
187+
188+
global.console.assert(1 + 1 === 2);
189+
});
190+
191+
it('should capture exception when console logs an error object with level set to "error"', () => {
192+
const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] });
193+
captureConsoleIntegration.setupOnce(
194+
() => undefined,
195+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
196+
);
197+
198+
const someError = new Error('some error');
199+
global.console.error(someError);
200+
201+
expect(mockHub.captureException).toHaveBeenCalledTimes(1);
202+
expect(mockHub.captureException).toHaveBeenCalledWith(someError);
203+
});
204+
205+
it('should capture exception on `console.error` when no levels are provided in constructor', () => {
206+
const captureConsoleIntegration = new CaptureConsole();
207+
captureConsoleIntegration.setupOnce(
208+
() => undefined,
209+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
210+
);
211+
212+
const someError = new Error('some error');
213+
global.console.error(someError);
214+
215+
expect(mockHub.captureException).toHaveBeenCalledTimes(1);
216+
expect(mockHub.captureException).toHaveBeenCalledWith(someError);
217+
});
218+
219+
it('should capture message on `console.log` when no levels are provided in constructor', () => {
220+
const captureConsoleIntegration = new CaptureConsole();
221+
captureConsoleIntegration.setupOnce(
222+
() => undefined,
223+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
224+
);
225+
226+
global.console.error('some message');
227+
228+
expect(mockHub.captureMessage).toHaveBeenCalledTimes(1);
229+
expect(mockHub.captureMessage).toHaveBeenCalledWith('some message');
230+
});
231+
232+
it('should capture message when console logs a non-error object with level set to "error"', () => {
233+
const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] });
234+
captureConsoleIntegration.setupOnce(
235+
() => undefined,
236+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
237+
);
238+
239+
global.console.error('some non-error message');
240+
241+
expect(mockHub.captureMessage).toHaveBeenCalledTimes(1);
242+
expect(mockHub.captureMessage).toHaveBeenCalledWith('some non-error message');
243+
expect(mockHub.captureException).not.toHaveBeenCalled();
244+
});
245+
246+
it('should capture a message for non-error log levels', () => {
247+
const captureConsoleIntegration = new CaptureConsole({ levels: ['info'] });
248+
captureConsoleIntegration.setupOnce(
249+
() => undefined,
250+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
251+
);
252+
253+
global.console.info('some message');
254+
255+
expect(mockHub.captureMessage).toHaveBeenCalledTimes(1);
256+
expect(mockHub.captureMessage).toHaveBeenCalledWith('some message');
257+
});
258+
259+
it('should call the original console function when console members are called', () => {
260+
// Mock console log to test if it was called
261+
const originalConsoleLog = global.console.log;
262+
const mockConsoleLog = jest.fn();
263+
global.console.log = mockConsoleLog;
264+
265+
const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] });
266+
captureConsoleIntegration.setupOnce(
267+
() => undefined,
268+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
269+
);
270+
271+
global.console.log('some message 1', 'some message 2');
272+
273+
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
274+
expect(mockConsoleLog).toHaveBeenCalledWith('some message 1', 'some message 2');
275+
276+
// Reset console log
277+
global.console.log = originalConsoleLog;
278+
});
279+
280+
it('should not wrap any levels that are not members of console', () => {
281+
const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'someNonExistingLevel', 'error'] });
282+
captureConsoleIntegration.setupOnce(
283+
() => undefined,
284+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
285+
);
286+
287+
// The provided level should not be created
288+
expect(global.console['someNonExistingLevel']).toBeUndefined();
289+
290+
// Ohter levels should be wrapped as expected
291+
expect(global.console.log).not.toBe(originalConsole.log);
292+
expect(global.console.error).not.toBe(originalConsole.error);
293+
});
294+
295+
it('should wrap the console when the client does not have a registered captureconsole integration, but not capture any messages', () => {
296+
const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'error'] });
297+
captureConsoleIntegration.setupOnce(
298+
() => undefined,
299+
() => getMockHubWithIntegration(null) as any, // simulate not having the integration registered
300+
);
301+
302+
// Console should be wrapped
303+
expect(global.console.log).not.toBe(originalConsole.log);
304+
expect(global.console.error).not.toBe(originalConsole.error);
305+
306+
// Should not capture messages
307+
global.console.log('some message');
308+
expect(mockHub.captureMessage).not.toHaveBeenCalledWith();
309+
});
310+
311+
it("should not crash when the original console methods don't exist at time of invocation", () => {
312+
const originalConsoleLog = global.console.log;
313+
global.console.log = undefined; // don't `delete` here, otherwise `fill` won't wrap the function
314+
315+
const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] });
316+
captureConsoleIntegration.setupOnce(
317+
() => undefined,
318+
() => getMockHubWithIntegration(captureConsoleIntegration) as any,
319+
);
320+
321+
expect(() => {
322+
global.console.log('some message');
323+
}).not.toThrow();
324+
325+
global.console.log = originalConsoleLog;
326+
});
327+
});

0 commit comments

Comments
 (0)