From 502bf985adf9c526c049e55a1e70f9382b90a870 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 2 Nov 2023 14:39:42 +0100 Subject: [PATCH 1/7] chore: add @ngneat/falso as a dev dependency --- package-lock.json | 21 +++++++++++++++++++++ package.json | 1 + 2 files changed, 22 insertions(+) diff --git a/package-lock.json b/package-lock.json index 8100c62efaa8..dceedbc86fdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -139,6 +139,7 @@ "@dword-design/eslint-plugin-import-alias": "^4.0.8", "@electron/notarize": "^2.1.0", "@jest/globals": "^29.5.0", + "@ngneat/falso": "^7.1.1", "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", @@ -6001,6 +6002,16 @@ "@types/react-native": "*" } }, + "node_modules/@ngneat/falso": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@ngneat/falso/-/falso-7.1.1.tgz", + "integrity": "sha512-/5HuJDaZHXl3WVdgvYBAM52OSYbSKfiNazVOZOw/3KjeZ6dQW9F0QCG+W6z52lUu5MZvp/TkPGaVRtoz6h9T1w==", + "dev": true, + "dependencies": { + "seedrandom": "3.0.5", + "uuid": "8.3.2" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -57385,6 +57396,16 @@ "csstype": "^3.0.8" } }, + "@ngneat/falso": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@ngneat/falso/-/falso-7.1.1.tgz", + "integrity": "sha512-/5HuJDaZHXl3WVdgvYBAM52OSYbSKfiNazVOZOw/3KjeZ6dQW9F0QCG+W6z52lUu5MZvp/TkPGaVRtoz6h9T1w==", + "dev": true, + "requires": { + "seedrandom": "3.0.5", + "uuid": "8.3.2" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index 31a1088b740b..6a2e862e899b 100644 --- a/package.json +++ b/package.json @@ -188,6 +188,7 @@ "@babel/runtime": "^7.20.0", "@electron/notarize": "^2.1.0", "@jest/globals": "^29.5.0", + "@ngneat/falso": "^7.1.1", "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", From 7a6ddad7a37372059f2de65b94a99b111bb4d918 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 2 Nov 2023 20:28:02 +0100 Subject: [PATCH 2/7] feat: add createRandomCollection generic utility --- tests/utils/collections/createRandomCollection.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/utils/collections/createRandomCollection.ts diff --git a/tests/utils/collections/createRandomCollection.ts b/tests/utils/collections/createRandomCollection.ts new file mode 100644 index 000000000000..7711c64004be --- /dev/null +++ b/tests/utils/collections/createRandomCollection.ts @@ -0,0 +1,11 @@ +export default function createRandomCollection(createKey: (index: number) => string, createItem: (index: number) => T, length = 500): Record { + const map: Record = {}; + + for (let i = 0; i < length; i++) { + const item = createItem(i); + const itemKey = createKey(i); + map[itemKey] = item; + } + + return map; +} From 32b18c3d2ca69ad7ae2957705f89bbee3b4b77e2 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 2 Nov 2023 21:13:46 +0100 Subject: [PATCH 3/7] chore: change the api on createCollection util --- tests/utils/collections/createCollection.ts | 11 +++++++++++ tests/utils/collections/createRandomCollection.ts | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 tests/utils/collections/createCollection.ts delete mode 100644 tests/utils/collections/createRandomCollection.ts diff --git a/tests/utils/collections/createCollection.ts b/tests/utils/collections/createCollection.ts new file mode 100644 index 000000000000..565957b7649e --- /dev/null +++ b/tests/utils/collections/createCollection.ts @@ -0,0 +1,11 @@ +export default function createCollection(createKey: (item: T, index: number) => string, createItem: (index: number) => T, length = 500): Record { + const map: Record = {}; + + for (let i = 0; i < length; i++) { + const item = createItem(i); + const itemKey = createKey(item, i); + map[itemKey] = item; + } + + return map; +} diff --git a/tests/utils/collections/createRandomCollection.ts b/tests/utils/collections/createRandomCollection.ts deleted file mode 100644 index 7711c64004be..000000000000 --- a/tests/utils/collections/createRandomCollection.ts +++ /dev/null @@ -1,11 +0,0 @@ -export default function createRandomCollection(createKey: (index: number) => string, createItem: (index: number) => T, length = 500): Record { - const map: Record = {}; - - for (let i = 0; i < length; i++) { - const item = createItem(i); - const itemKey = createKey(i); - map[itemKey] = item; - } - - return map; -} From fedab6cfc38ac7705502e7d4040023e1d3f6f024 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 2 Nov 2023 21:14:12 +0100 Subject: [PATCH 4/7] feat: add createRandomPolicy util --- tests/utils/collections/policies.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/utils/collections/policies.ts diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts new file mode 100644 index 000000000000..d8d2c6f5bf1d --- /dev/null +++ b/tests/utils/collections/policies.ts @@ -0,0 +1,26 @@ +import {rand, randAvatar, randBoolean, randCurrencyCode, randEmail, randPastDate, randWord} from '@ngneat/falso'; +import CONST from '../../../src/CONST'; +import type {Policy} from '../../../src/types/onyx'; + +export default function createRandomPolicy(index: number): Policy { + return { + id: index.toString(), + name: randWord(), + type: rand(Object.values(CONST.POLICY.TYPE)), + areChatRoomsEnabled: randBoolean(), + autoReporting: randBoolean(), + isPolicyExpenseChatEnabled: randBoolean(), + autoReportingFrequency: rand(Object.values(CONST.POLICY.AUTO_REPORTING_FREQUENCIES)), + outputCurrency: randCurrencyCode(), + role: rand(Object.values(CONST.POLICY.ROLE)), + owner: randEmail(), + ownerAccountID: index, + avatar: randAvatar(), + isFromFullPolicy: randBoolean(), + lastModified: randPastDate().toISOString(), + pendingAction: rand(Object.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)), + errors: {}, + customUnits: {}, + errorFields: {}, + }; +} From 075611bbb0e651f2c6f46ea032fef5bed914a9d9 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 2 Nov 2023 21:15:23 +0100 Subject: [PATCH 5/7] chore: fix imports --- tests/utils/collections/policies.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts index d8d2c6f5bf1d..266c8bba2d72 100644 --- a/tests/utils/collections/policies.ts +++ b/tests/utils/collections/policies.ts @@ -1,6 +1,6 @@ import {rand, randAvatar, randBoolean, randCurrencyCode, randEmail, randPastDate, randWord} from '@ngneat/falso'; -import CONST from '../../../src/CONST'; -import type {Policy} from '../../../src/types/onyx'; +import CONST from '@src/CONST'; +import type {Policy} from '@src/types/onyx'; export default function createRandomPolicy(index: number): Policy { return { From d3ee4e2f1faefaeb5cb1a0583b0e26262d8165d1 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 3 Nov 2023 11:01:01 +0100 Subject: [PATCH 6/7] chore: add basic randomized report mock --- tests/utils/collections/reports.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/utils/collections/reports.ts diff --git a/tests/utils/collections/reports.ts b/tests/utils/collections/reports.ts new file mode 100644 index 000000000000..a52df9e1df41 --- /dev/null +++ b/tests/utils/collections/reports.ts @@ -0,0 +1,22 @@ +import {rand, randBoolean, randCurrencyCode, randEmail, randWord} from '@ngneat/falso'; +import CONST from '@src/CONST'; +import type {Report} from '@src/types/onyx'; + +export default function createRandomReport(index: number): Report { + return { + reportID: index.toString(), + chatType: rand(Object.values(CONST.REPORT.CHAT_TYPE)), + currency: randCurrencyCode(), + displayName: randWord(), + hasDraft: randBoolean(), + ownerEmail: randEmail(), + ownerAccountID: index, + isPinned: randBoolean(), + isOptimisticReport: randBoolean(), + isOwnPolicyExpenseChat: randBoolean(), + isWaitingOnBankAccount: randBoolean(), + isLastMessageDeletedParentAction: randBoolean(), + policyID: index.toString(), + reportName: randWord(), + }; +} From 193cf19b6db5df8376e4fa2f5bcdcc8e7530ffe5 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Mon, 6 Nov 2023 15:25:36 +0100 Subject: [PATCH 7/7] docs: add section on mocking collections within tests/readme --- tests/README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/README.md b/tests/README.md index dd5b5fc1635f..6170006cb9bb 100644 --- a/tests/README.md +++ b/tests/README.md @@ -15,6 +15,36 @@ - To simulate a network request succeeding or failing we can mock the expected response first and then manually trigger the action that calls that API command. - [Mocking the response of `HttpUtils.xhr()`](https://github.com/Expensify/App/blob/ca2fa88a5789b82463d35eddc3d57f70a7286868/tests/actions/SessionTest.js#L25-L32) is the best way to simulate various API conditions so we can verify whether a result occurs or not. +## Mocking collections / collection items + +When unit testing an interface with Jest/performance testing with Reassure you might need to work with collections of data. These often get tricky to generate and maintain. To help with this we have a few helper methods located in `tests/utils/collections/`. + +- `createCollection()` - Creates a collection of data (`Record`) with a given number of items (default=500). This is useful for eg. testing the performance of a component with a large number of items. You can use it to populate Onyx. +- `createRandom*()` - like `createRandomPolicy`, these functions are responsible for generating a randomised object of the given type. You can use them as your defaults when calling `createCollection()` or as standalone utilities. + +Basic example: +```ts +const policies = createCollection(item => `policies_${item.id}`, createRandomPolicy); + +/** + Output: + { + "policies_0": policyItem0, + "policies_1": policyItem1, + ... + } +*/ +``` + +Example with overrides: + +```ts +const policies = createCollection( + item => `policies_${item.id}`, + index => ({ ...createRandomPolicy(index), isPinned: true }) +); +``` + ## Mocking `node_modules`, user modules, and what belongs in `jest/setup.js` If you need to mock a library that exists in `node_modules` then add it to the `__mocks__` folder in the root of the project. More information about this [here](https://jestjs.io/docs/manual-mocks#mocking-node-modules). If you need to mock an individual library you should create a mock module in a `__mocks__` subdirectory adjacent to the library as explained [here](https://jestjs.io/docs/manual-mocks#mocking-user-modules). However, keep in mind that when you do this you also must manually require the mock by calling something like `jest.mock('../../src/libs/Log');` at the top of an individual test file. If every test in the app will need something to be mocked that's a good case for adding it to `jest/setup.js`, but we should generally avoid adding user mocks or `node_modules` mocks to this file. Please use the `__mocks__` subdirectories wherever appropriate.