Skip to content

Commit

Permalink
added basic unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
miquelbeltran committed Nov 19, 2024
1 parent a69821d commit aafffa2
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 44 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/sdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ jobs:
- name: Run npm Install
run: npm install

# TODO: Check Lint
- name: Unit Tests
run: npm run test

6 changes: 5 additions & 1 deletion sdk/__mocks__/@react-native-async-storage/async-storage.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
export * from '@react-native-async-storage/async-storage/jest/async-storage-mock';

var storage = {};

export default {
getItem: (item, value = null) => {
return new Promise((resolve, reject) => {
resolve(value);
storage[item] ? resolve(storage[item]) :
resolve(value);
});
},
setItem: (item, value) => {
return new Promise((resolve, reject) => {
storage[item] = value;
resolve(value);
});
}
Expand Down
52 changes: 42 additions & 10 deletions sdk/__tests__/RaygunClient.test.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,60 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { init, sendError } from '../src/RaygunClient';
import { RaygunClientOptions } from '../src/Types';
import { cleanup } from '@testing-library/react-native';

// jest.mock('../RaygunClient');
// jest.useFakeTimers();

describe('RaygunClient', () => {
// let raygunClient: RaygunClient;

// beforeEach(() => {
// NativeModules.RaygunNativeBridge = { DEVICE_ID: 'test-device-id' };
// });

it('should send error correctly', async () => {
beforeAll(() => {
const options: RaygunClientOptions = {
apiKey: '',// Your API key
version: '', // Your application version
logLevel: 'off',
enableCrashReporting: true,
enableRealUserMonitoring: false,
disableNativeCrashReporting: true,
};

init(options);

global.fetch = jest.fn(() =>
Promise.resolve({
status: 200,
})
);
});

beforeEach(() => {
fetch.mockClear();
});

afterEach(cleanup);

it('should send error correctly', async () => {
const error = new Error('Test error');
await sendError(error);

// expect(raygunClient.sendError).toHaveBeenCalledWith(error);
// fetch should be called once
expect(fetch).toHaveBeenCalledTimes(1);

// Capture body from fetch and check if correct
const body = JSON.parse(fetch.mock.calls[0][1].body);
expect(body.Details.Error.Message).toBe('Test error');
});

it('should fail to send error', async () => {
fetch.mockImplementationOnce(() => Promise.reject('API is down'));
const error = new Error('Failed error');
await sendError(error);

expect(fetch).toHaveBeenCalledTimes(1);

// failed to send error should be stored in AsyncStorage
const storedErrors = await AsyncStorage.getItem('raygun4reactnative_local_storage');
expect(storedErrors).not.toBeNull();
console.log(storedErrors);

const errors = JSON.parse(storedErrors);
expect(errors[0].Details.Error.Message).toBe('Failed error');
});
});
64 changes: 32 additions & 32 deletions sdk/src/CrashReporter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {cleanFilePath, filterOutReactFrames, getCurrentTags, getCurrentUser, noAddressAt, upperFirst} from './Utils';
import {BeforeSendHandler, Breadcrumb, CrashReportPayload, CustomData, ManualCrashReportDetails} from './Types';
import {StackFrame} from 'react-native/Libraries/Core/Devtools/parseErrorStack';
import {NativeModules, Platform} from 'react-native';
import { cleanFilePath, filterOutReactFrames, getCurrentTags, getCurrentUser, noAddressAt, upperFirst } from './Utils';
import { BeforeSendHandler, Breadcrumb, CrashReportPayload, CustomData, ManualCrashReportDetails } from './Types';
import { StackFrame } from 'react-native/Libraries/Core/Devtools/parseErrorStack';
import { NativeModules, Platform } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import RaygunLogger from './RaygunLogger';

const {RaygunNativeBridge} = NativeModules;
const {version: clientVersion} = require('../package.json');
const { RaygunNativeBridge } = NativeModules;
const { version: clientVersion } = require('../package.json');

/**
* The Crash Reporter is responsible for all of the functionality related to generating, catching
Expand Down Expand Up @@ -78,7 +78,7 @@ export default class CrashReporter {
});

if (!disableUnhandledPromiseRejectionReporting) {
const {polyfillGlobal} = require('react-native/Libraries/Utilities/PolyfillFunctions');
const { polyfillGlobal } = require('react-native/Libraries/Utilities/PolyfillFunctions');
const Promise = require('promise/setimmediate/es6-extensions');
const tracking = require('promise/setimmediate/rejection-tracking');
require('promise/setimmediate/done');
Expand All @@ -95,17 +95,17 @@ export default class CrashReporter {
});
}

this.resendCachedReports().then((r) => {});
this.resendCachedReports().then((r) => { });
}

/**
* Append some custom data to the users current set of custom data.
* @param {CustomData} customData - The custom data to append.
*/
setCustomData(customData: CustomData) {
this.customData = {...customData};
this.customData = { ...customData };
if (!this.disableNativeCrashReporting) {
RaygunNativeBridge.setCustomData({...customData});
RaygunNativeBridge.setCustomData({ ...customData });
}
}

Expand Down Expand Up @@ -134,7 +134,7 @@ export default class CrashReporter {

breadcrumb.message = JSON.stringify(breadcrumb.message);

this.breadcrumbs.push({...breadcrumb});
this.breadcrumbs.push({ ...breadcrumb });

if (this.breadcrumbs.length > this.maxBreadcrumbsPerErrorReport) {
this.breadcrumbs.shift();
Expand Down Expand Up @@ -165,14 +165,14 @@ export default class CrashReporter {
/**
* Retrieve and format the local Crash Report cache as a JSON array.
*/
async getCachedCrashReports() : Promise<CrashReportPayload[]> {
async getCachedCrashReports(): Promise<CrashReportPayload[]> {
try {
const rawCache = await AsyncStorage.getItem(this.LOCAL_STORAGE_KEY);
if (rawCache !== null) {
try {
return JSON.parse(rawCache);
} catch (e: any) {
RaygunLogger.e('Unable to extract payload from cache:', {error: e.message, cache: rawCache});
RaygunLogger.e('Unable to extract payload from cache:', { error: e.message, cache: rawCache });
}
}
} catch (e: any) {
Expand All @@ -185,7 +185,7 @@ export default class CrashReporter {
* Override the local Crash Report cache with a new JSON array.
* @param {CrashReportPayload[]} newCache - The new JSON array to override with.
*/
async setCachedCrashReports(newCache : CrashReportPayload[]) {
async setCachedCrashReports(newCache: CrashReportPayload[]) {
try {
await AsyncStorage.setItem(this.LOCAL_STORAGE_KEY, JSON.stringify(newCache));
} catch (e) {
Expand All @@ -198,7 +198,7 @@ export default class CrashReporter {
* @param {CrashReportPayload[]} reports - Reports to append.
*/
async cacheCrashReports(...reports: CrashReportPayload[]) {
let appendedCache : CrashReportPayload[] = (await this.getCachedCrashReports()).concat(reports);
let appendedCache: CrashReportPayload[] = (await this.getCachedCrashReports()).concat(reports);

// If the cache is already full then ignore this report
if (appendedCache.length >= this.maxErrorReportsStoredOnDevice) {
Expand All @@ -212,16 +212,16 @@ export default class CrashReporter {
* Attempt to send all cached reports, re-caching any that fail to send.
*/
async resendCachedReports() {
const cache : CrashReportPayload[] = await this.getCachedCrashReports();
const reCache : CrashReportPayload[] = [];
const cache: CrashReportPayload[] = await this.getCachedCrashReports();
const reCache: CrashReportPayload[] = [];

for (let i = 0; i < cache.length; i++) {
await this.sendCrashReport(cache[i]).then((success) => {
if (!success) reCache.push(cache[i]);
});
}

this.setCachedCrashReports(reCache);
await this.setCachedCrashReports(reCache);
}

/**
Expand All @@ -234,7 +234,7 @@ export default class CrashReporter {
Math.max(newSize, 0), CrashReporter.MAX_ERROR_REPORTS_STORED_ON_DEVICE);

// Remove excess cached reports where necessary, prioritising older reports
const cache : CrashReportPayload[] = await this.getCachedCrashReports();
const cache: CrashReportPayload[] = await this.getCachedCrashReports();
if (cache.length > this.maxErrorReportsStoredOnDevice) {
await this.setCachedCrashReports(cache.slice(0, this.maxErrorReportsStoredOnDevice));
}
Expand All @@ -259,7 +259,7 @@ export default class CrashReporter {
payload.Details.Tags = getCurrentTags().concat('Fatal');
}

this.managePayload(payload);
await this.managePayload(payload);
}

/**
Expand All @@ -276,18 +276,18 @@ export default class CrashReporter {

const payload = await this.generateCrashReportPayload(error, stack);

const payloadWithLocalParams: CrashReportPayload = {...payload};
const payloadWithLocalParams: CrashReportPayload = { ...payload };

if (details) {
if (details.customData) {
payloadWithLocalParams.Details.UserCustomData = Object.assign({...this.customData}, details.customData);
payloadWithLocalParams.Details.UserCustomData = Object.assign({ ...this.customData }, details.customData);
}
if (details.tags) {
payloadWithLocalParams.Details.Tags = getCurrentTags().concat(details.tags);
}
}

this.managePayload(payloadWithLocalParams);
await this.managePayload(payloadWithLocalParams);
}

/**
Expand Down Expand Up @@ -316,7 +316,7 @@ export default class CrashReporter {
* Modifies and sends the Crash Report Payload (manages beforeSendHandler).
* @param {CrashReportPayload} payload - The payload to send away.
*/
managePayload(payload: CrashReportPayload) {
async managePayload(payload: CrashReportPayload) {
const modifiedPayload =
this.onBeforeSendingCrashReport && typeof this.onBeforeSendingCrashReport === 'function' ?
this.onBeforeSendingCrashReport(Object.freeze(payload)) :
Expand All @@ -329,12 +329,12 @@ export default class CrashReporter {
RaygunLogger.v('Crash Report Payload:', modifiedPayload);

// Send the Crash Report, caching it if the transmission is not successful
this.sendCrashReport(modifiedPayload).then((success) => {
if (!success) this.cacheCrashReports(modifiedPayload);
else {
this.resendCachedReports();
}
});
var success = await this.sendCrashReport(modifiedPayload);
if (!success) {
await this.cacheCrashReports(modifiedPayload);
} else {
await this.resendCachedReports();
}
}

/**
Expand All @@ -361,7 +361,7 @@ export default class CrashReporter {
(RaygunNativeBridge.getEnvironmentInfo && (await RaygunNativeBridge.getEnvironmentInfo())) || {};

// Reformat Native Stack frames to the Raygun StackTrace format
const convertToCrashReportingStackFrame = ({file, methodName, lineNumber, column}: StackFrame) => ({
const convertToCrashReportingStackFrame = ({ file, methodName, lineNumber, column }: StackFrame) => ({
FileName: file,
MethodName: methodName || '[anonymous]',
LineNumber: lineNumber,
Expand Down Expand Up @@ -401,7 +401,7 @@ export default class CrashReporter {
* Transmit a Crash Report payload to raygun, returning whether or not the transmission is successful.
* @param {CrashReportPayload} payload
*/
async sendCrashReport(payload: CrashReportPayload) : Promise<boolean> {
async sendCrashReport(payload: CrashReportPayload): Promise<boolean> {
// Send the message
try {
return await fetch(this.raygunCrashReportEndpoint + '?apiKey=' + encodeURIComponent(this.apiKey), {
Expand Down

0 comments on commit aafffa2

Please sign in to comment.