mirror of
https://github.com/fergalmoran/onearmy-community-platform.git
synced 2025-12-22 09:37:54 +00:00
165 lines
5.0 KiB
Markdown
165 lines
5.0 KiB
Markdown
---
|
||
id: tests
|
||
title: Tests
|
||
---
|
||
|
||
Backend tests are written with [Jest](https://jestjs.io/docs/getting-started) and executed against a set of emulators for core services
|
||
|
||
## Prerequisites
|
||
|
||
You will need to support the running of [Firebase Emulators](https://firebase.google.com/docs/emulator-suite/install_and_configure#install_the_local_emulator_suite)
|
||
|
||
## Running Locally
|
||
|
||
```
|
||
yarn workspace functions test:watch
|
||
```
|
||
|
||
This will startup the firebase emulator suite, compile functions, and watch for any changes to function or spec test files
|
||
|
||
:::info
|
||
The emulator console can be viewed on [localhost:4001](http://localhost:4001)
|
||
:::
|
||
|
||
To run just a single test the interactive prompts can be used to provide a matching filename path, e.g.
|
||
|
||
```sh
|
||
$ yarn workspace functions test:watch
|
||
|
||
$ Watch Usage
|
||
> Press p to filter by a filename regex pattern
|
||
|
||
$ pattern › mySpec
|
||
|
||
```
|
||
|
||
This will automatically watch for changes to any files that match the regular expression `/mySpec/`
|
||
|
||
## Writing Tests
|
||
|
||
Tests are written in [Jest](https://jestjs.io/docs/getting-started), with filenames ending `.spec.ts`
|
||
|
||
## Utilities and Examples
|
||
|
||
### Mocking Methods
|
||
|
||
Mocks are a useful tool to reduce the number of additional services a test interacts with, making them more resilient to changes in the codebase.
|
||
|
||
There are various ways to achieve this, many of which are outlines in the Jest [Mock Functions](https://jestjs.io/docs/mock-functions) documentation
|
||
|
||
A few methods commonly used in the codebase include:
|
||
|
||
**Imports**
|
||
Return a set of mock methods or utilities in place of an import. An example from the frontend code is when we want to avoid importing all stores, but instead just mock a single method
|
||
|
||
```ts
|
||
jest.mock('src/common/hooks/useCommonStores', () => {
|
||
return {
|
||
useCommonStores() {
|
||
return {
|
||
stores: {
|
||
userStore: {
|
||
fetchAllVerifiedUsers: jest.fn(),
|
||
},
|
||
},
|
||
}
|
||
},
|
||
}
|
||
})
|
||
```
|
||
|
||
**Class Methods**
|
||
Replace specific class method with an alternative mock method. E.g. if the code relates to the active user, a mock stub could be used instead. An an artificial example:
|
||
|
||
```ts
|
||
import UserMethods from './userMethods'
|
||
|
||
UserMethods.activeUser = jest.fn().mockReturnValue({ id: 'fake_user' })
|
||
```
|
||
|
||
**Replace or extend multiple class methods with mocks**
|
||
Similarly, entire mock classes can be used where appropriate
|
||
|
||
```ts
|
||
import UserMethods from './userMethods'
|
||
|
||
class MockUserMethods implements Partial<UserMethods>{
|
||
activeUser: () => jest.fn().mockReturnValue({ id: 'fake_user' })
|
||
setUser: () => jest.fn()
|
||
}
|
||
```
|
||
|
||
### Execute Functions Directly
|
||
|
||
It is possible to directly execute any function within the test environment without its required trigger.
|
||
A utility `FirebaseEmulatedTest` class is used to wrap the function invocation so that it can be used async, e.g.
|
||
|
||
_myFunction.ts_
|
||
|
||
```ts
|
||
import functions from 'firebase-functions'
|
||
|
||
exports.default = functions.firestore
|
||
.document(`mockEndpoint/{id}`)
|
||
.onUpdate((change) => {
|
||
return doSomething(change)
|
||
})
|
||
```
|
||
|
||
_myFunction.spec.ts_
|
||
|
||
```ts
|
||
import { FirebaseEmulatedTest } from '../test/Firebase/emulator'
|
||
import myFunction from './myFunction.ts'
|
||
|
||
const beforeData = { field: 'initialValue' }
|
||
const afterData = { field: 'changedValue' }
|
||
|
||
const change = FirebaseEmulatedTest.mockFirestoreChangeObject(
|
||
beforeData,
|
||
afterData,
|
||
'mockEndpoint',
|
||
'mockDocId',
|
||
)
|
||
|
||
await FirebaseEmulatedTest.run(myFunction, change)
|
||
```
|
||
|
||
Additional utilities can also be used to provide a mock of the triggering context. In the example above a mock `before` and `after` data snapshot is created to mimic the firestore document change trigger used by the function
|
||
|
||
### Seed and Teardown Data
|
||
|
||
The test utilities also have methods for seeding and clearing the Firestore DB if required for tests.
|
||
|
||
_myFunction.spec.ts_
|
||
|
||
```ts
|
||
beforeEach(async () => {
|
||
await FirebaseEmulatedTest.seedFirestoreDB('users', [{ _id: 'user1 ' }])
|
||
})
|
||
afterEach(async () => {
|
||
await FirebaseEmulatedTest.clearFirestoreDB()
|
||
})
|
||
```
|
||
|
||
Additionally all emulator data will be cleared when scripts are terminated
|
||
|
||
:::caution
|
||
If your test function interacts directly with an empty database endpoint it may throw an error
|
||
(various issues on github, should review in the future)
|
||
:::
|
||
|
||
The easiest workaround is to seed the endpoint without any docs, which will allow the emulators to return an empty array from queries.
|
||
|
||
```
|
||
await FirebaseEmulatedTest.seedFirestoreDB('empty_endpoint',[])
|
||
```
|
||
|
||
### Production Data
|
||
|
||
There may also be some cases where methods want to be tested against production data to check for any additional edge-cases or give a preview new feature development.
|
||
|
||
There currently isn't a single automated way to do this, however you can see an example of the manual steps involved in the `test_functions` step of the [CircleCI pipeline](https://github.com/ONEARMY/community-platform/blob/feat/aggregation-tests/.circleci/config.yml#L244-L245)
|
||
|
||
Alternatively developers can follow the steps in [Firebase Emulators Docker](./firebase-emulator.md) to run the docker emulators locally and manually invoke functions
|