Skip to content

Commit

Permalink
test: Adds SDK unit tests and CI task (#105)
Browse files Browse the repository at this point in the history
* WIP test setup

* WIP test setup

* mocked native bridge

* mock async storage

* setup tests done

* mock asyncstorage

* added basic unit tests

* Cleanup

* end of file new line

* add more checks

* prettier

* add prettier job

* add path

* prettier

* fix paths

* setup working directory

* working directory fix

* working directory

* fix prettier
  • Loading branch information
miquelbeltran authored Nov 21, 2024
1 parent 1b68259 commit a398d24
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 172 deletions.
26 changes: 25 additions & 1 deletion .github/workflows/sdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,28 @@ jobs:
- name: Run npm Install
run: npm install

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

# Checks code formatting, fails if there are changes after applying prettier.
# Based on this example here:
# https://github.com/creyD/prettier_action?tab=readme-ov-file#example-4-dry-run
prettier:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Make sure the actual branch is checked out when running on pull requests
ref: ${{ github.head_ref }}
# Make sure the value of GITHUB_TOKEN will not be persisted in repo's config
persist-credentials: false

- name: Prettify code
uses: creyD/[email protected]
with:
# "dry" causes that if any file is modified, the job fails
dry: True
# "write" performs changes in place
prettier_options: --write sdk/**/*.js sdk/**/*.ts sdk/**/*.tsx
github_token: ${{ secrets.PERSONAL_GITHUB_TOKEN }}
17 changes: 17 additions & 0 deletions sdk/__mocks__/@react-native-async-storage/async-storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +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) => {
storage[item] ? resolve(storage[item]) : resolve(value);
});
},
setItem: (item, value) => {
return new Promise((resolve, reject) => {
storage[item] = value;
resolve(value);
});
}
};
5 changes: 5 additions & 0 deletions sdk/__mocks__/RaygunNativeBridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NativeModules } from 'react-native';

NativeModules.RaygunNativeBridge = {
DEVICE_ID: '1234567890'
};
60 changes: 60 additions & 0 deletions sdk/__tests__/RaygunClient.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { init, sendError } from '../src/RaygunClient';
import { RaygunClientOptions } from '../src/Types';

describe('RaygunClient', () => {
beforeAll(() => {
const options: RaygunClientOptions = {
apiKey: 'ABCD',
version: '1.2.3',
logLevel: 'off',
enableCrashReporting: true,
enableRealUserMonitoring: false,
disableNativeCrashReporting: true
};
init(options);

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

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

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

// fetch should be called once
expect(fetch).toHaveBeenCalledTimes(1);

// Check url correct
expect(fetch.mock.calls[0][0]).toBe('https://api.raygun.com/entries?apiKey=ABCD');

// 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');

// Check if the version is correct
expect(body.Details.Version).toBe('1.2.3');
});

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();

const errors = JSON.parse(storedErrors);
expect(errors[0].Details.Error.Message).toBe('Failed error');
});
});
4 changes: 4 additions & 0 deletions sdk/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['babel-plugin-syntax-hermes-parser']
};
12 changes: 6 additions & 6 deletions sdk/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/src'],
preset: 'react-native',
transform: {
'^.+\\.tsx?$': 'ts-jest'
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { rootMode: 'upward' }]
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
clearMocks: true
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|react-navigation|@react-navigation|@react-native-community|@react-native-firebase|@react-navigation/stack|@react-navigation/bottom-tabs|@react-navigation/drawer|@react-navigation/native|@react-navigation/material-bottom-tabs|@react-navigation/material-top-tabs|@react-navigation/stack|@react-navigation/web))'
],
setupFiles: ['./__mocks__/RaygunNativeBridge.js']
};
6 changes: 5 additions & 1 deletion sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"run_demo_3:windows": "cd ../demo && npx react-native run-android",
"run_demo_3:macos": "cd ../demo && cd ios && pod install && cd ../ && npx react-native run-ios",
"run_demo_3:linux": "cd ../demo && gnome-terminal -x bash -c \"npx react-native start\" && npx react-native run-android",
"prettier": "npx prettier --write src"
"prettier": "npx prettier --write **/*.js __mocks__/**/*.js **/*.ts **/*.tsx",
"test": "jest"
},
"repository": {
"type": "git",
Expand All @@ -54,6 +55,8 @@
"@types/react-native": "^0.72.8",
"@typescript-eslint/eslint-plugin": "^8.13.0",
"@typescript-eslint/parser": "^8.13.0",
"babel-plugin-syntax-hermes-parser": "^0.25.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"copyfiles": "^2.4.1",
"eslint": "^9.14.0",
"eslint-config-google": "^0.14.0",
Expand All @@ -66,6 +69,7 @@
"glob": "^11.0.0",
"jest": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
"metro-react-native-babel-preset": "^0.77.0",
"mockdate": "^3.0.2",
"react": "^18.3.1",
"react-native": "^0.76.1",
Expand Down
2 changes: 1 addition & 1 deletion sdk/react-native.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module.exports = {
platforms: {
ios: {},
android: {
packageInstance: "new RaygunNativeBridgePackage()"
packageInstance: 'new RaygunNativeBridgePackage()'
}
}
}
Expand Down
Loading

0 comments on commit a398d24

Please sign in to comment.