Skip to content

Commit

Permalink
misc(tests): Add type check to tests (#4017)
Browse files Browse the repository at this point in the history
  • Loading branch information
krystofwoldrich authored Aug 16, 2024
1 parent ee3fa22 commit baa0217
Show file tree
Hide file tree
Showing 26 changed files with 159 additions and 80 deletions.
16 changes: 10 additions & 6 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ module.exports = {
setupFilesAfterEnv: ['jest-extended/all', '<rootDir>/test/mockConsole.ts'],
globals: {
__DEV__: true,
'ts-jest': {
tsConfig: './tsconfig.json',
diagnostics: false,
},
},
moduleFileExtensions: ['ts', 'tsx', 'js'],
transform: {
'^.+\\.jsx$': 'babel-jest',
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: 'tsconfig.json',
},
],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testPathIgnorePatterns: ['<rootDir>/test/e2e/', '<rootDir>/test/tools/', '<rootDir>/test/react-native/versions'],
testEnvironment: 'node',
testMatch: ['**/*.test.(ts|tsx)'],
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@sentry-internal/eslint-plugin-sdk": "8.11.0",
"@sentry-internal/typescript": "8.11.0",
"@sentry/wizard": "3.16.3",
"@testing-library/react-native": "^12.5.3",
"@types/jest": "^29.5.3",
"@types/node": "^20.9.3",
"@types/react": "^18.2.64",
Expand Down
2 changes: 1 addition & 1 deletion src/js/profiling/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const hermesProfilingIntegration: IntegrationFn = () => {
startTimestampNs: number;
}
| undefined;
let _currentProfileTimeout: number | undefined;
let _currentProfileTimeout: ReturnType<typeof setTimeout> | undefined;
let isReady: boolean = false;

const setupOnce = (): void => {
Expand Down
3 changes: 2 additions & 1 deletion src/js/tools/metroconfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { logger } from '@sentry/utils';
import type { MetroConfig, MixedOutput, Module, ReadOnlyGraph } from 'metro';
import type { MixedOutput, Module, ReadOnlyGraph } from 'metro';
import type { MetroConfig } from 'metro-config';
import * as process from 'process';
import { env } from 'process';

Expand Down
1 change: 0 additions & 1 deletion src/js/tracing/reactnavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ export const reactNavigationIntegration = ({
}

const routeHasBeenSeen = recentRouteKeys.includes(route.key);

const latestTtidSpan =
!routeHasBeenSeen &&
enableTimeToInitialDisplay &&
Expand Down
15 changes: 7 additions & 8 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as mockedtimetodisplaynative from './tracing/mockedtimetodisplaynative'
jest.mock('../src/js/tracing/timetodisplaynative', () => mockedtimetodisplaynative);

import { defaultStackParser } from '@sentry/browser';
import type { Envelope, Event, Outcome, Transport } from '@sentry/types';
import type { Envelope, Event, Outcome, Transport, TransportMakeRequestResponse } from '@sentry/types';
import { rejectedSyncPromise, SentryError } from '@sentry/utils';
import * as RN from 'react-native';

Expand Down Expand Up @@ -104,7 +104,6 @@ describe('Tests ReactNativeClient', () => {
});

await expect(client.eventFromMessage('test')).resolves.toBeDefined();
// @ts-expect-error: Is Mocked
await expect(RN.LogBox.ignoreLogs).toBeCalled();
});

Expand Down Expand Up @@ -133,7 +132,7 @@ describe('Tests ReactNativeClient', () => {
});

test('use custom transport function', async () => {
const mySend = (_request: Envelope) => Promise.resolve();
const mySend = (_request: Envelope): Promise<TransportMakeRequestResponse> => Promise.resolve({});
const myFlush = (timeout?: number) => Promise.resolve(Boolean(timeout));
const myCustomTransportFn = (): Transport => ({
send: mySend,
Expand Down Expand Up @@ -408,7 +407,7 @@ describe('Tests ReactNativeClient', () => {

describe('event data enhancement', () => {
test('event contains sdk default information', async () => {
const mockedSend = jest.fn<PromiseLike<void>, [Envelope]>().mockResolvedValue(undefined);
const mockedSend = jest.fn<PromiseLike<TransportMakeRequestResponse>, [Envelope]>().mockResolvedValue({});
const mockedTransport = (): Transport => ({
send: mockedSend,
flush: jest.fn().mockResolvedValue(true),
Expand Down Expand Up @@ -436,7 +435,7 @@ describe('Tests ReactNativeClient', () => {

describe('normalizes events', () => {
test('handles circular input', async () => {
const mockedSend = jest.fn<PromiseLike<void>, [Envelope]>();
const mockedSend = jest.fn<PromiseLike<TransportMakeRequestResponse>, [Envelope]>().mockResolvedValue({});
const mockedTransport = (): Transport => ({
send: mockedSend,
flush: jest.fn().mockResolvedValue(true),
Expand Down Expand Up @@ -469,7 +468,7 @@ describe('Tests ReactNativeClient', () => {

describe('clientReports', () => {
test('does not send client reports if disabled', () => {
const mockTransportSend = jest.fn((_envelope: Envelope) => Promise.resolve());
const mockTransportSend = jest.fn<PromiseLike<TransportMakeRequestResponse>, [Envelope]>().mockResolvedValue({});
const client = new ReactNativeClient({
...DEFAULT_OPTIONS,
dsn: EXAMPLE_DSN,
Expand All @@ -488,7 +487,7 @@ describe('Tests ReactNativeClient', () => {
});

test('send client reports on event envelope', () => {
const mockTransportSend = jest.fn((_envelope: Envelope) => Promise.resolve());
const mockTransportSend = jest.fn<PromiseLike<TransportMakeRequestResponse>, [Envelope]>().mockResolvedValue({});
const client = new ReactNativeClient({
...DEFAULT_OPTIONS,
dsn: EXAMPLE_DSN,
Expand Down Expand Up @@ -522,7 +521,7 @@ describe('Tests ReactNativeClient', () => {
});

test('does not send empty client report', () => {
const mockTransportSend = jest.fn((_envelope: Envelope) => Promise.resolve());
const mockTransportSend = jest.fn<PromiseLike<TransportMakeRequestResponse>, [Envelope]>().mockResolvedValue({});
const client = new ReactNativeClient({
...DEFAULT_OPTIONS,
dsn: EXAMPLE_DSN,
Expand Down
3 changes: 1 addition & 2 deletions test/integrations/spotlight.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { HttpRequestEventMap } from '@mswjs/interceptors';
import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest';
import type { Client, Envelope } from '@sentry/types';
import { XMLHttpRequest } from 'xmlhttprequest';

import { spotlightIntegration } from '../../src/js/integrations/spotlight';

globalThis.XMLHttpRequest = XMLHttpRequest;
globalThis.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
const requestListener = jest.fn<void, HttpRequestEventMap['request']>();
const interceptor = new XMLHttpRequestInterceptor();
interceptor.on('request', requestListener);
Expand Down
2 changes: 2 additions & 0 deletions test/mockConsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ global.console = {
warn: jest.fn(),
error: jest.fn(),
};

export {};
5 changes: 5 additions & 0 deletions test/mockWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ const NATIVE: MockInterface<NativeType> = {
fetchNativeStackFramesBy: jest.fn(),

initNativeReactNavigationNewFrameTracking: jest.fn(),

captureReplay: jest.fn(),
getCurrentReplayId: jest.fn(),
};

NATIVE.isNativeAvailable.mockReturnValue(true);
Expand All @@ -74,6 +77,8 @@ NATIVE.stopProfiling.mockReturnValue(null);
NATIVE.fetchNativePackageName.mockReturnValue('mock-native-package-name');
NATIVE.fetchNativeStackFramesBy.mockReturnValue(null);
NATIVE.initNativeReactNavigationNewFrameTracking.mockReturnValue(Promise.resolve());
NATIVE.captureReplay.mockResolvedValue(null);
NATIVE.getCurrentReplayId.mockReturnValue(null);

export const getRNSentryModule = jest.fn();

Expand Down
1 change: 1 addition & 0 deletions test/scopeSync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ describe('ScopeSync', () => {

it('addBreadcrumb', () => {
expect(SentryCore.getIsolationScope().addBreadcrumb).not.toBe(addBreadcrumbScopeSpy);
SentryCore.getIsolationScope().getLastBreadcrumb = jest.fn(() => ({ message: 'test' }));

SentryCore.addBreadcrumb({ message: 'test' });
expect(NATIVE.addBreadcrumb).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ message: 'test' }));
Expand Down
16 changes: 8 additions & 8 deletions test/sdk.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
jest.spyOn(logger, 'error');
jest.mock('../src/js/wrapper', () => jest.requireActual('./mockWrapper'));
jest.mock('../src/js/utils/environment');
jest.mock('@sentry/core', () => ({
...jest.requireActual('@sentry/core'),
initAndBind: jest.fn(),
}));

import { initAndBind } from '@sentry/core';
import { makeFetchTransport } from '@sentry/react';
import type { BaseTransportOptions, ClientOptions, Integration, Scope } from '@sentry/types';
Expand All @@ -19,6 +11,14 @@ import { getDefaultEnvironment, isExpoGo, notWeb } from '../src/js/utils/environ
import { NATIVE } from './mockWrapper';
import { firstArg, secondArg } from './testutils';

jest.spyOn(logger, 'error');
jest.mock('../src/js/wrapper', () => jest.requireActual('./mockWrapper'));
jest.mock('../src/js/utils/environment');
jest.mock('@sentry/core', () => ({
...jest.requireActual('@sentry/core'),
initAndBind: jest.fn(),
}));

describe('Tests the SDK functionality', () => {
beforeEach(() => {
(NATIVE.isNativeAvailable as jest.Mock).mockImplementation(() => true);
Expand Down
4 changes: 2 additions & 2 deletions test/sdk.withclient.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
jest.spyOn(logger, 'error');

import { setCurrentClient } from '@sentry/core';
import { logger } from '@sentry/utils';

import { flush } from '../src/js/sdk';
import { getDefaultTestClientOptions, TestClient } from './mocks/client';

jest.spyOn(logger, 'error');

describe('Tests the SDK functionality', () => {
let client: TestClient;

Expand Down
12 changes: 0 additions & 12 deletions test/testutils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Transaction } from '@sentry/core';
import type { Session, Transport, UserFeedback } from '@sentry/types';
import { rejectedSyncPromise } from '@sentry/utils';

import { getBlankTransactionContext } from '../src/js/tracing/utils';

export type MockInterface<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer B ? jest.Mock<B, A> : T[K];
} & T;
Expand All @@ -13,15 +10,6 @@ export function mockFunction<T extends (...args: any[]) => any>(fn: T): jest.Moc
return fn as jest.MockedFunction<T>;
}

export const getMockTransaction = (name: string): Transaction => {
const transaction = new Transaction(getBlankTransactionContext(name));

// Assume it's sampled
transaction.sampled = true;

return transaction;
};

export const firstArg = 0;
export const secondArg = 1;
export const envelopeHeader = 0;
Expand Down
14 changes: 13 additions & 1 deletion test/tools/metroconfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ jest.mock('fs', () => {
};
});

import type { getDefaultConfig } from 'expo/metro-config';
import * as fs from 'fs';
import type { MetroConfig } from 'metro';
import * as path from 'path';
import * as process from 'process';

import { withSentryBabelTransformer, withSentryFramesCollapsed } from '../../src/js/tools/metroconfig';
import {
getSentryExpoConfig,
withSentryBabelTransformer,
withSentryFramesCollapsed,
} from '../../src/js/tools/metroconfig';

type MetroFrame = Parameters<Required<Required<MetroConfig>['symbolicator']>['customizeFrame']>[0];

Expand All @@ -20,6 +25,13 @@ describe('metroconfig', () => {
jest.clearAllMocks();
});

test('getSentryExpoConfig keeps compatible interface with Expos getDefaultConfig', () => {
const acceptsExpoDefaultConfigFactory = (_factory: typeof getDefaultConfig): void => {
expect(true).toBe(true);
};
acceptsExpoDefaultConfigFactory(getSentryExpoConfig);
});

describe('withSentryFramesCollapsed', () => {
test('adds customizeFrames if undefined ', () => {
const config = withSentryFramesCollapsed({});
Expand Down
4 changes: 2 additions & 2 deletions test/tracing/integrations/nativeframes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ describe('NativeFramesInstrumentation', () => {
});

it('does not set measurements on transactions without startFrames', async () => {
const startFrames = null;
const startFrames: null = null;
const finishFrames = {
totalFrames: 200,
slowFrames: 40,
Expand Down Expand Up @@ -191,7 +191,7 @@ describe('NativeFramesInstrumentation', () => {
slowFrames: 20,
frozenFrames: 5,
};
const finishFrames = null;
const finishFrames: null = null;
mockFunction(NATIVE.fetchNativeFrames).mockResolvedValueOnce(startFrames).mockResolvedValueOnce(finishFrames);

await startSpan({ name: 'test' }, async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@ import type { AppStateStatus } from 'react-native';

import { stallTrackingIntegration } from '../../../../src/js/tracing/integrations/stalltracking';

type StallTrackingWithTestProperties = ReturnType<typeof stallTrackingIntegration> & {
isTracking: boolean;
_internalState: {
isBackground: boolean;
lastIntervalMs: number;
timeout: ReturnType<typeof setTimeout> | null;
iteration: () => void;
backgroundEventListener: (event: string) => void;
};
};

describe('BackgroundEventListener', () => {
it('Stall tracking should set _isBackground to false, update _lastIntervalMs, and call _iteration when state is active and _timeout is not null', () => {
const stallTracking = stallTrackingIntegration();
const stallTracking = stallTrackingIntegration() as StallTrackingWithTestProperties;
const LOOP_TIMEOUT_INTERVAL_MS = 500; // Change this value based on your actual interval value
const currentTime = Date.now();
stallTracking['_internalState']['lastIntervalMs'] = currentTime;
Expand All @@ -18,14 +29,14 @@ describe('BackgroundEventListener', () => {
jest.runOnlyPendingTimers(); // Fast-forward the timer to execute the timeout function
});
it('Stall tracking should set _isBackground to true when state is not active', () => {
const stallTracking = stallTrackingIntegration();
const stallTracking = stallTrackingIntegration() as StallTrackingWithTestProperties;
stallTracking['_internalState']['isBackground'] = false;
stallTracking['_internalState']['backgroundEventListener']('background' as AppStateStatus);
// Check if _isBackground is set to true
expect(stallTracking['_internalState']['isBackground']).toBe(true);
});
it('Stall tracking should not call _iteration when state is active but _timeout is null', () => {
const stallTracking = stallTrackingIntegration();
const stallTracking = stallTrackingIntegration() as StallTrackingWithTestProperties;
stallTracking['_internalState']['timeout'] = null;
// Mock _iteration
stallTracking['_internalState']['iteration'] = jest.fn();
Expand All @@ -35,7 +46,7 @@ describe('BackgroundEventListener', () => {
expect(stallTracking['_internalState']['iteration']).not.toBeCalled();
});
it('Stall tracking should call _iteration when state is active and _timeout is defined', () => {
const stallTracking = stallTrackingIntegration();
const stallTracking = stallTrackingIntegration() as StallTrackingWithTestProperties;
stallTracking['_internalState']['timeout'] = setTimeout(() => {}, 500);
// Mock _iteration
stallTracking['_internalState']['iteration'] = jest.fn(); // Create a fake timeout to simulate a running interval
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import { stallTrackingIntegration } from '../../../../src/js/tracing/integrations/stalltracking';

type StallTrackingWithTestProperties = ReturnType<typeof stallTrackingIntegration> & {
isTracking: boolean;
_internalState: {
isBackground: boolean;
lastIntervalMs: number;
timeout: ReturnType<typeof setTimeout> | null;
stallCount: number;
totalStallTime: number;
statsByTransaction: Map<string, { count: number; total: number }>;
iteration: () => void;
};
};

describe('Iteration', () => {
it('Stall tracking does not set _timeout when isTracking is false', () => {
const stallTracking = stallTrackingIntegration();
const stallTracking = stallTrackingIntegration() as StallTrackingWithTestProperties;
stallTracking['isTracking'] = false;
stallTracking['_internalState']['isBackground'] = false;
stallTracking['_internalState']['lastIntervalMs'] = Date.now() - 1000; // Force a timeout
Expand All @@ -12,7 +25,7 @@ describe('Iteration', () => {
expect(stallTracking['_internalState']['timeout']).toBeNull();
});
it('Stall tracking does not set _timeout when isBackground is true', () => {
const stallTracking = stallTrackingIntegration();
const stallTracking = stallTrackingIntegration() as StallTrackingWithTestProperties;
stallTracking['isTracking'] = true;
stallTracking['_internalState']['isBackground'] = true;
stallTracking['_internalState']['lastIntervalMs'] = Date.now() - 1000; // Force a timeout
Expand All @@ -22,7 +35,7 @@ describe('Iteration', () => {
expect(stallTracking['_internalState']['timeout']).toBeNull();
});
it('Stall tracking should set _timeout when isTracking is true and isBackground false', () => {
const stallTracking = stallTrackingIntegration();
const stallTracking = stallTrackingIntegration() as StallTrackingWithTestProperties;
stallTracking['isTracking'] = true;
stallTracking['_internalState']['isBackground'] = false;
jest.useFakeTimers();
Expand All @@ -32,7 +45,7 @@ describe('Iteration', () => {
expect(stallTracking['_internalState']['timeout']).toBeDefined();
});
it('Stall tracking should update _stallCount and _totalStallTime when timeout condition is met', () => {
const stallTracking = stallTrackingIntegration();
const stallTracking = stallTrackingIntegration() as StallTrackingWithTestProperties;
const LOOP_TIMEOUT_INTERVAL_MS = 50;
const _minimumStallThreshold = 100;
// Call _iteration with totalTimeTaken >= LOOP_TIMEOUT_INTERVAL_MS + _minimumStallThreshold
Expand Down
2 changes: 1 addition & 1 deletion test/tracing/mockedtimetodisplaynative.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import { View } from 'react-native';

import type { RNSentryOnDrawNextFrameEvent, RNSentryOnDrawReporterProps } from '../../src/js/tracing/timetodisplaynative.types';
Expand Down
Loading

0 comments on commit baa0217

Please sign in to comment.