├── .circleci └── config.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .husky ├── _ │ └── husky.sh ├── post-commit └── pre-commit ├── .npmrc ├── .nvmrc ├── CHANGELOG.md ├── eslint.config.mjs ├── jest-mongodb-config-repl.js ├── jest-mongodb-config.js ├── jest-preset.js ├── license ├── package.json ├── readme.md ├── renovate.json ├── src ├── environment.ts ├── helpers.ts ├── index.ts ├── setup.ts ├── teardown.ts └── types.ts ├── test ├── mongo-aggregate.test.ts ├── mongo-insert.test.ts ├── mongo-parallelism.test.ts ├── mongo-parallelism2.test.ts └── mongo-parallelism3.test.ts └── tsconfig.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | node: circleci/node@7.1.0 5 | 6 | parameters: 7 | node_version: 8 | type: string 9 | default: '22.13.0' 10 | 11 | commands: 12 | install_deps: 13 | steps: 14 | - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc 15 | - node/install-pnpm: 16 | version: 9.15.4 17 | - node/install-packages: 18 | pkg-manager: pnpm 19 | cache-only-lockfile: true 20 | # Default command is 'pnpm install --frozen-lockfile'. 21 | # 'pnpm install' fails with --frozen-lockfile option because it can't find the lockfile 22 | override-ci-command: pnpm install 23 | 24 | jobs: 25 | build_and_test: 26 | executor: 27 | name: node/default 28 | tag: << pipeline.parameters.node_version >> 29 | working_directory: ~/repo 30 | steps: 31 | - checkout 32 | - install_deps 33 | - run: pnpm run type-check 34 | - run: pnpm run lint:ci 35 | - run: pnpm run build 36 | - run: pnpm run test 37 | - run: pnpm run test:repl 38 | 39 | workflows: 40 | build_test: 41 | jobs: 42 | - build_and_test 43 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | coverage/ 3 | node_modules/ 4 | lib/ 5 | temp 6 | yarn.lock 7 | *.log 8 | .DS_Store 9 | globalConfig.json 10 | !.husky/_/husky.sh 11 | -------------------------------------------------------------------------------- /.husky/_/husky.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$husky_skip_init" ]; then 3 | debug () { 4 | if [ "$HUSKY_DEBUG" = "1" ]; then 5 | echo "husky (debug) - $1" 6 | fi 7 | } 8 | 9 | readonly hook_name="$(basename "$0")" 10 | debug "starting $hook_name..." 11 | 12 | if [ "$HUSKY" = "0" ]; then 13 | debug "HUSKY env variable is set to 0, skipping hook" 14 | exit 0 15 | fi 16 | 17 | if [ -f ~/.huskyrc ]; then 18 | debug "sourcing ~/.huskyrc" 19 | . ~/.huskyrc 20 | fi 21 | 22 | export readonly husky_skip_init=1 23 | sh -e "$0" "$@" 24 | exitCode="$?" 25 | 26 | if [ $exitCode != 0 ]; then 27 | echo "husky - $hook_name hook exited with code $exitCode (error)" 28 | fi 29 | 30 | exit $exitCode 31 | fi 32 | -------------------------------------------------------------------------------- /.husky/post-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | git update-index --again 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | pnpm exec lint-staged 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.15.1 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 5.2.1 4 | 5 | - Updated dependencies: 6 | - debug to v4.4.1 7 | - mongodb-memory-server to v10 8 | - Updated Node.js to v22.15.1 9 | - Refactored isMongoMemoryReplSetOptions to helpers.ts 10 | 11 | # Breaking Changes 12 | 13 | ## 5.0.0 14 | 15 | - Switched `node` version `18`->`22` 16 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import rules from '@shelf/eslint-config/typescript.js'; 2 | 3 | export default [ 4 | ...rules, 5 | { 6 | rules: { 7 | '@typescript-eslint/no-var-requires': 'off', 8 | }, 9 | }, 10 | {files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.json']}, 11 | { 12 | ignores: [ 13 | '.idea/', 14 | 'coverage/', 15 | 'draft.js', 16 | 'lib/', 17 | 'dist/', 18 | 'node_modules/', 19 | 'packages/**/tsconfig.types.json', 20 | 'packages/**/node_modules/**', 21 | 'packages/**/lib/**', 22 | 'renovate.json', 23 | ], 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /jest-mongodb-config-repl.js: -------------------------------------------------------------------------------- 1 | /** @type {import('./src/types').Config} */ 2 | module.exports = { 3 | mongodbMemoryServerOptions: { 4 | binary: { 5 | skipMD5: true, 6 | }, 7 | autoStart: false, 8 | instance: {}, 9 | replSet: { 10 | count: 4, 11 | storageEngine: 'wiredTiger', 12 | }, 13 | }, 14 | mongoURLEnvName: 'MONGO_URL', 15 | }; 16 | -------------------------------------------------------------------------------- /jest-mongodb-config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('./src/types').Config} */ 2 | module.exports = { 3 | mongodbMemoryServerOptions: { 4 | binary: { 5 | skipMD5: true, 6 | }, 7 | autoStart: false, 8 | instance: {}, 9 | }, 10 | mongoURLEnvName: 'MONGO_URL', 11 | }; 12 | -------------------------------------------------------------------------------- /jest-preset.js: -------------------------------------------------------------------------------- 1 | const preset = require('./lib').default; 2 | 3 | module.exports = preset; 4 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Gemshelf Inc. (shelf.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@shelf/jest-mongodb", 3 | "version": "5.2.2", 4 | "description": "Run your tests using Jest & MongoDB in Memory server", 5 | "keywords": [ 6 | "jest", 7 | "jest environment", 8 | "jest preset", 9 | "mongodb", 10 | "mongodb local" 11 | ], 12 | "repository": "shelfio/jest-mongodb", 13 | "license": "MIT", 14 | "author": { 15 | "name": "Vlad Holubiev", 16 | "email": "vlad@shelf.io", 17 | "url": "shelf.io" 18 | }, 19 | "files": [ 20 | "jest-preset.js", 21 | "lib/" 22 | ], 23 | "scripts": { 24 | "build": "rm -rf lib/ && pnpm run build:types && babel src --out-dir lib --ignore '**/*.test.ts' --extensions '.ts'", 25 | "build:types": "tsc --emitDeclarationOnly --declaration --isolatedModules false --declarationDir lib", 26 | "lint": "pnpm run lint:ci --fix", 27 | "lint:ci": "eslint . --quiet", 28 | "prepack": "pnpm run build", 29 | "test": "jest", 30 | "test:repl": "MONGO_MEMORY_SERVER_FILE=jest-mongodb-config-repl.js jest", 31 | "type-check": "tsc --noEmit", 32 | "type-check:watch": "pnpm run type-check --watch" 33 | }, 34 | "lint-staged": { 35 | "*.{html,md,yml}": [ 36 | "prettier --write" 37 | ], 38 | "*.{ts,js,json}": [ 39 | "eslint --fix" 40 | ] 41 | }, 42 | "babel": { 43 | "extends": "@shelf/babel-config/backend" 44 | }, 45 | "prettier": "@shelf/prettier-config", 46 | "jest": { 47 | "preset": "./jest-preset.js" 48 | }, 49 | "dependencies": { 50 | "debug": "4.4.1", 51 | "mongodb-memory-server": "10.1.4" 52 | }, 53 | "devDependencies": { 54 | "@babel/cli": "7.27.2", 55 | "@babel/core": "7.27.4", 56 | "@jest/environment": "29.7.0", 57 | "@shelf/babel-config": "3.0.0", 58 | "@shelf/eslint-config": "4.4.0", 59 | "@shelf/prettier-config": "1.0.0", 60 | "@shelf/tsconfig": "0.1.0", 61 | "@types/jest": "29.5.14", 62 | "@types/node": "22", 63 | "eslint": "9.28.0", 64 | "husky": "9.1.7", 65 | "jest": "29.7.0", 66 | "jest-environment-node": "29.7.0", 67 | "lint-staged": "16.1.0", 68 | "mongodb": "6.16.0", 69 | "prettier": "3.5.3", 70 | "typescript": "5.8.3" 71 | }, 72 | "peerDependencies": { 73 | "jest-environment-node": "28.x || 29.x", 74 | "mongodb": "3.x.x || 4.x || 5.x || 6.x" 75 | }, 76 | "engines": { 77 | "node": ">=22" 78 | }, 79 | "publishConfig": { 80 | "access": "public" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # jest-mongodb [![CircleCI](https://circleci.com/gh/shelfio/jest-mongodb/tree/master.svg?style=svg)](https://circleci.com/gh/shelfio/jest-mongodb/tree/master) ![](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) [![npm (scoped)](https://img.shields.io/npm/v/@shelf/jest-mongodb.svg)](https://www.npmjs.com/package/@shelf/jest-mongodb) 2 | 3 | > Jest preset to run MongoDB memory server 4 | 5 | ## Usage 6 | 7 | ### 0. Install 8 | 9 | ``` 10 | $ yarn add @shelf/jest-mongodb --dev 11 | ``` 12 | 13 | Make sure `mongodb` is installed in the project as well, as it's required as a peer dependency. 14 | 15 | ### 1. Create `jest.config.js` 16 | 17 | ```js 18 | module.exports = { 19 | preset: '@shelf/jest-mongodb', 20 | }; 21 | ``` 22 | 23 | If you have a custom `jest.config.js` make sure you remove `testEnvironment` property, otherwise it will conflict with the preset. 24 | 25 | ### 2. Create `jest-mongodb-config.js` 26 | 27 | See [mongodb-memory-server](https://github.com/nodkz/mongodb-memory-server#available-options) 28 | 29 | ```js 30 | module.exports = { 31 | mongodbMemoryServerOptions: { 32 | binary: { 33 | version: '4.0.3', 34 | skipMD5: true, 35 | }, 36 | autoStart: false, 37 | instance: {}, 38 | }, 39 | }; 40 | ``` 41 | 42 | To use the same database for all tests pass the config like this: 43 | 44 | ```js 45 | module.exports = { 46 | mongodbMemoryServerOptions: { 47 | binary: { 48 | version: '4.0.3', 49 | skipMD5: true, 50 | }, 51 | instance: { 52 | dbName: 'jest', 53 | }, 54 | autoStart: false, 55 | }, 56 | }; 57 | ``` 58 | 59 | To use separate database for each jest worker pass the `useSharedDBForAllJestWorkers: false` (doesn't create `process.env` variable when using this option): 60 | 61 | ```js 62 | module.exports = { 63 | mongodbMemoryServerOptions: { 64 | binary: { 65 | skipMD5: true, 66 | }, 67 | autoStart: false, 68 | instance: {}, 69 | }, 70 | 71 | useSharedDBForAllJestWorkers: false, 72 | }; 73 | ``` 74 | 75 | To use dynamic database name you must pass empty object for instance field: 76 | 77 | ```js 78 | module.exports = { 79 | mongodbMemoryServerOptions: { 80 | binary: { 81 | version: '4.0.3', 82 | skipMD5: true, 83 | }, 84 | instance: {}, 85 | autoStart: false, 86 | }, 87 | }; 88 | ``` 89 | 90 | To use another uri environment variable name you must set mongoURLEnvName field: 91 | 92 | ```js 93 | module.exports = { 94 | mongodbMemoryServerOptions: { 95 | binary: { 96 | version: '4.0.3', 97 | skipMD5: true, 98 | }, 99 | instance: {}, 100 | autoStart: false, 101 | }, 102 | mongoURLEnvName: 'MONGODB_URI', 103 | }; 104 | ``` 105 | 106 | To use mongo as a replica set you must add the `replSet` config object and set 107 | `count` and `storageEngine` fields: 108 | 109 | ```js 110 | module.exports = { 111 | mongodbMemoryServerOptions: { 112 | binary: { 113 | skipMD5: true, 114 | }, 115 | autoStart: false, 116 | instance: {}, 117 | replSet: { 118 | count: 3, 119 | storageEngine: 'wiredTiger', 120 | }, 121 | }, 122 | }; 123 | ``` 124 | 125 | ### 3. Configure MongoDB client 126 | 127 | Library sets the `process.env.MONGO_URL` for your convenience, but using of `global.__MONGO_URI__` is preferable as it works with ` useSharedDBForAllJestWorkers: false` 128 | 129 | ```js 130 | const {MongoClient} = require('mongodb'); 131 | 132 | describe('insert', () => { 133 | let connection; 134 | let db; 135 | 136 | beforeAll(async () => { 137 | connection = await MongoClient.connect(global.__MONGO_URI__, { 138 | useNewUrlParser: true, 139 | useUnifiedTopology: true, 140 | }); 141 | db = await connection.db(); 142 | }); 143 | 144 | afterAll(async () => { 145 | await connection.close(); 146 | }); 147 | }); 148 | ``` 149 | 150 | ### 4. PROFIT! Write tests 151 | 152 | ```js 153 | it('should insert a doc into collection', async () => { 154 | const users = db.collection('users'); 155 | 156 | const mockUser = {_id: 'some-user-id', name: 'John'}; 157 | await users.insertOne(mockUser); 158 | 159 | const insertedUser = await users.findOne({_id: 'some-user-id'}); 160 | expect(insertedUser).toEqual(mockUser); 161 | }); 162 | ``` 163 | 164 | Cache MongoDB binary in CI by putting this folder to the list of cached paths: `./node_modules/.cache/mongodb-memory-server/mongodb-binaries` 165 | 166 | You can enable debug logs by setting environment variable `DEBUG=jest-mongodb:*` 167 | 168 | #### 5. Clean collections before each test (optional) 169 | 170 | ```js 171 | beforeEach(async () => { 172 | await db.collection('COLLECTION_NAME').deleteMany({}); 173 | }); 174 | ``` 175 | 176 | See [this issue](https://github.com/shelfio/jest-mongodb/issues/173) for discussion 177 | 178 | #### 6. Jest watch mode gotcha 179 | 180 | This package creates the file `globalConfig.json` in the project root, when using jest `--watch` flag, changes to `globalConfig.json` can cause an infinite loop 181 | 182 | In order to avoid this unwanted behaviour, add `globalConfig` to ignored files in watch mode in the Jest configuation 183 | 184 | ```js 185 | // jest.config.js 186 | module.exports = { 187 | watchPathIgnorePatterns: ['globalConfig'], 188 | }; 189 | ``` 190 | 191 | ## See Also 192 | 193 | - [jest-dynamodb](https://github.com/shelfio/jest-dynamodb) 194 | 195 | ## Publish 196 | 197 | ```sh 198 | $ git checkout master 199 | $ pnpm version 200 | $ pnpm publish 201 | $ git push origin master --tags 202 | ``` 203 | 204 | ## License 205 | 206 | MIT © [Shelf](https://shelf.io) 207 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>shelfio/renovate-config-public"], 3 | "labels": ["backend"] 4 | } 5 | -------------------------------------------------------------------------------- /src/environment.ts: -------------------------------------------------------------------------------- 1 | import {join as pathJoin} from 'path'; 2 | import {readFileSync} from 'fs'; 3 | import {randomUUID} from 'crypto'; 4 | import {TestEnvironment} from 'jest-environment-node'; 5 | import {MongoMemoryReplSet, MongoMemoryServer} from 'mongodb-memory-server'; 6 | import type {EnvironmentContext} from '@jest/environment'; 7 | import type {JestEnvironmentConfig} from '@jest/environment'; 8 | import {getMongodbMemoryOptions, isMongoMemoryReplSetOptions} from './helpers'; 9 | 10 | const debug = require('debug')('jest-mongodb:environment'); 11 | 12 | module.exports = class MongoEnvironment extends TestEnvironment { 13 | globalConfigPath: string; 14 | mongo: MongoMemoryReplSet | MongoMemoryServer; 15 | constructor(config: JestEnvironmentConfig, context: EnvironmentContext) { 16 | super(config, context); 17 | this.globalConfigPath = pathJoin(config.globalConfig.rootDir, 'globalConfig.json'); 18 | 19 | const options = getMongodbMemoryOptions(config.globalConfig.rootDir); 20 | const isReplSet = isMongoMemoryReplSetOptions(options); 21 | debug(`isReplSet`, isReplSet); 22 | 23 | this.mongo = isReplSet ? new MongoMemoryReplSet(options) : new MongoMemoryServer(options); 24 | } 25 | 26 | async setup() { 27 | debug('Setup MongoDB Test Environment'); 28 | 29 | const globalConfig = JSON.parse(readFileSync(this.globalConfigPath, 'utf-8')); 30 | 31 | if (globalConfig.mongoUri) { 32 | this.global.__MONGO_URI__ = globalConfig.mongoUri; 33 | } else { 34 | await this.mongo.start(); 35 | 36 | this.global.__MONGO_URI__ = this.mongo.getUri(); 37 | } 38 | 39 | this.global.__MONGO_DB_NAME__ = globalConfig.mongoDBName || randomUUID(); 40 | 41 | await super.setup(); 42 | } 43 | 44 | async teardown() { 45 | debug('Teardown MongoDB Test Environment'); 46 | 47 | await this.mongo.stop(); 48 | 49 | await super.teardown(); 50 | } 51 | 52 | // @ts-ignore 53 | runScript(script) { 54 | // @ts-ignore 55 | return super.runScript(script); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import {resolve} from 'path'; 2 | import type {MongoMemoryReplSet, MongoMemoryServer} from 'mongodb-memory-server'; 3 | import type {Config} from './types'; 4 | 5 | const configFile = process.env.MONGO_MEMORY_SERVER_FILE || 'jest-mongodb-config.js'; 6 | 7 | type MongoMemoryReplSetOpts = NonNullable[0]>; 8 | type MongoMemoryServerOpts = NonNullable[0]>; 9 | 10 | export function isMongoMemoryReplSetOptions( 11 | options?: MongoMemoryReplSetOpts | MongoMemoryServerOpts 12 | ): options is MongoMemoryReplSetOpts { 13 | return Boolean((options as MongoMemoryReplSetOpts).replSet); 14 | } 15 | 16 | export function getMongodbMemoryOptions( 17 | cwd: string 18 | ): MongoMemoryReplSetOpts | MongoMemoryServerOpts | undefined { 19 | try { 20 | const {mongodbMemoryServerOptions}: Config = require(resolve(cwd, configFile)); 21 | 22 | return mongodbMemoryServerOptions; 23 | } catch { 24 | return { 25 | binary: { 26 | checkMD5: false, 27 | }, 28 | instance: {}, 29 | }; 30 | } 31 | } 32 | 33 | export function getMongoURLEnvName(cwd: string) { 34 | try { 35 | const {mongoURLEnvName}: Config = require(resolve(cwd, configFile)); 36 | 37 | return mongoURLEnvName || 'MONGO_URL'; 38 | } catch (e) { 39 | return 'MONGO_URL'; 40 | } 41 | } 42 | 43 | export function shouldUseSharedDBForAllJestWorkers(cwd: string) { 44 | try { 45 | const {useSharedDBForAllJestWorkers}: Config = require(resolve(cwd, configFile)); 46 | 47 | if (typeof useSharedDBForAllJestWorkers === 'undefined') { 48 | return true; 49 | } 50 | 51 | return useSharedDBForAllJestWorkers; 52 | } catch (e) { 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {resolve} from 'path'; 2 | 3 | export * from './types'; 4 | 5 | export default { 6 | globalSetup: resolve(__dirname, './setup.js'), 7 | globalTeardown: resolve(__dirname, './teardown.js'), 8 | testEnvironment: resolve(__dirname, './environment.js'), 9 | }; 10 | -------------------------------------------------------------------------------- /src/setup.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable multiline-ternary */ 2 | import {writeFileSync} from 'fs'; 3 | import {join} from 'path'; 4 | import {MongoMemoryReplSet, MongoMemoryServer} from 'mongodb-memory-server'; 5 | import type {JestEnvironmentConfig} from '@jest/environment'; 6 | import type {Mongo} from './types'; 7 | import { 8 | getMongoURLEnvName, 9 | getMongodbMemoryOptions, 10 | shouldUseSharedDBForAllJestWorkers, 11 | } from './helpers'; 12 | import {isMongoMemoryReplSetOptions} from './helpers'; 13 | 14 | const debug = require('debug')('jest-mongodb:setup'); 15 | 16 | module.exports = async (config: JestEnvironmentConfig['globalConfig']) => { 17 | const globalConfigPath = join(config.rootDir, 'globalConfig.json'); 18 | 19 | const mongoMemoryServerOptions = getMongodbMemoryOptions(config.rootDir); 20 | const isReplSet = isMongoMemoryReplSetOptions(mongoMemoryServerOptions); 21 | 22 | debug(`isReplSet ${isReplSet}`); 23 | 24 | // @ts-ignore 25 | const mongo: Mongo = isReplSet 26 | ? new MongoMemoryReplSet(mongoMemoryServerOptions) 27 | : new MongoMemoryServer(mongoMemoryServerOptions); 28 | 29 | const options = getMongodbMemoryOptions(config.rootDir); 30 | const mongoConfig: {mongoUri?: string; mongoDBName?: string} = {}; 31 | 32 | debug( 33 | `shouldUseSharedDBForAllJestWorkers: ${shouldUseSharedDBForAllJestWorkers(config.rootDir)}` 34 | ); 35 | 36 | // if we run one mongodb instance for all tests 37 | if (shouldUseSharedDBForAllJestWorkers(config.rootDir)) { 38 | if (!mongo.isRunning) { 39 | await mongo.start(); 40 | } 41 | 42 | const mongoURLEnvName = getMongoURLEnvName(config.rootDir); 43 | 44 | mongoConfig.mongoUri = await mongo.getUri(); 45 | 46 | process.env[mongoURLEnvName] = mongoConfig.mongoUri; 47 | 48 | // Set reference to mongod in order to close the server during teardown. 49 | global.__MONGOD__ = mongo; 50 | } 51 | 52 | mongoConfig.mongoDBName = isMongoMemoryReplSetOptions(options) ? '' : options?.instance?.dbName; 53 | 54 | // Write global config to disk because all tests run in different contexts. 55 | writeFileSync(globalConfigPath, JSON.stringify(mongoConfig)); 56 | debug('Config is written'); 57 | }; 58 | -------------------------------------------------------------------------------- /src/teardown.ts: -------------------------------------------------------------------------------- 1 | import {join} from 'path'; 2 | import {unlink} from 'fs'; 3 | import type {JestEnvironmentConfig} from '@jest/environment'; 4 | 5 | const debug = require('debug')('jest-mongodb:teardown'); 6 | 7 | module.exports = async function (config: JestEnvironmentConfig['globalConfig']) { 8 | const globalConfigPath = join(config.rootDir, 'globalConfig.json'); 9 | 10 | debug('Teardown mongod'); 11 | if (global.__MONGOD__) { 12 | await global.__MONGOD__.stop(); 13 | } 14 | unlink(globalConfigPath, err => { 15 | if (err) { 16 | debug('Config could not be deleted'); 17 | 18 | return; 19 | } 20 | debug('Config is deleted'); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { MongoMemoryReplSet, MongoMemoryServer } from "mongodb-memory-server"; 3 | 4 | declare global { 5 | var __MONGOD__: Mongo; 6 | var __MONGO_URI__: string; 7 | var __MONGO_DB_NAME__: string 8 | } 9 | 10 | export type Mongo = (MongoMemoryReplSet | MongoMemoryServer) & {isRunning: boolean} 11 | 12 | type MongoMemoryReplSetOpts = NonNullable[0]>; 13 | type MongoMemoryServerOpts = NonNullable[0]>; 14 | 15 | export interface Config { 16 | mongodbMemoryServerOptions?: MongoMemoryReplSetOpts | MongoMemoryServerOpts; 17 | /** 18 | * @default 'MONGO_URL' 19 | */ 20 | mongoURLEnvName?: string; 21 | /** 22 | * @default true 23 | */ 24 | useSharedDBForAllJestWorkers?: boolean; 25 | } 26 | -------------------------------------------------------------------------------- /test/mongo-aggregate.test.ts: -------------------------------------------------------------------------------- 1 | import {MongoClient} from 'mongodb'; 2 | import type {Db} from 'mongodb'; 3 | import '../src/types'; 4 | 5 | describe('insert', () => { 6 | const uri = global.__MONGO_URI__; 7 | let connection: MongoClient; 8 | let db: Db; 9 | 10 | beforeAll(async () => { 11 | // @ts-ignore 12 | connection = await MongoClient.connect(uri, { 13 | // @ts-ignore 14 | useNewUrlParser: true, 15 | useUnifiedTopology: true, 16 | }); 17 | db = await connection.db(); 18 | }); 19 | 20 | afterAll(async () => { 21 | await connection.close(); 22 | }); 23 | 24 | it('should aggregate docs from collection', async () => { 25 | const files = db.collection('files'); 26 | 27 | await files.insertMany([ 28 | {type: 'Document'}, 29 | {type: 'Video'}, 30 | {type: 'Image'}, 31 | {type: 'Document'}, 32 | {type: 'Image'}, 33 | {type: 'Document'}, 34 | ]); 35 | 36 | const topFiles = await files 37 | .aggregate([{$group: {_id: '$type', count: {$sum: 1}}}, {$sort: {count: -1}}]) 38 | .toArray(); 39 | 40 | expect(topFiles).toEqual([ 41 | {_id: 'Document', count: 3}, 42 | {_id: 'Image', count: 2}, 43 | {_id: 'Video', count: 1}, 44 | ]); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/mongo-insert.test.ts: -------------------------------------------------------------------------------- 1 | import {MongoClient} from 'mongodb'; 2 | import type {Db} from 'mongodb'; 3 | import '../src/types'; 4 | 5 | describe('insert', () => { 6 | const uri = global.__MONGO_URI__; 7 | let connection: MongoClient; 8 | let db: Db; 9 | 10 | beforeAll(async () => { 11 | // @ts-ignore 12 | connection = await MongoClient.connect(uri, { 13 | // @ts-ignore 14 | useNewUrlParser: true, 15 | useUnifiedTopology: true, 16 | }); 17 | db = await connection.db(); 18 | }); 19 | 20 | afterAll(async () => { 21 | await connection.close(); 22 | }); 23 | 24 | it('should insert a doc into collection', async () => { 25 | const users = db.collection('users'); 26 | 27 | const mockUser = {_id: 'some-user-id', name: 'John'}; 28 | // @ts-ignore 29 | await users.insertOne(mockUser); 30 | 31 | const insertedUser = await users.findOne({_id: 'some-user-id'}); 32 | 33 | expect(insertedUser).toEqual(mockUser); 34 | }); 35 | 36 | it('should insert many docs into collection', async () => { 37 | const users = db.collection('users'); 38 | 39 | const mockUsers = [{name: 'Alice'}, {name: 'Bob'}]; 40 | await users.insertMany(mockUsers); 41 | 42 | const insertedUsers = await users.find().toArray(); 43 | 44 | expect(insertedUsers).toEqual([ 45 | expect.objectContaining({name: 'John'}), 46 | expect.objectContaining({name: 'Alice'}), 47 | expect.objectContaining({name: 'Bob'}), 48 | ]); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/mongo-parallelism.test.ts: -------------------------------------------------------------------------------- 1 | import {MongoClient} from 'mongodb'; 2 | import type {Db} from 'mongodb'; 3 | import '../src/types'; 4 | import {shouldUseSharedDBForAllJestWorkers} from '../src/helpers'; 5 | 6 | describe('parallelism: first worker', () => { 7 | const uri = global.__MONGO_URI__; 8 | let connection: MongoClient; 9 | let db: Db; 10 | 11 | beforeAll(async () => { 12 | // @ts-ignore 13 | connection = await MongoClient.connect(uri, { 14 | // @ts-ignore 15 | useNewUrlParser: true, 16 | useUnifiedTopology: true, 17 | }); 18 | db = await connection.db(); 19 | }); 20 | 21 | afterAll(async () => { 22 | await connection.close(); 23 | }); 24 | 25 | it('should have separate database', async () => { 26 | const collection = db.collection('parallelism-test'); 27 | 28 | await collection.insertOne({a: 1}); 29 | const count = await collection.count({}); 30 | 31 | if (!shouldUseSharedDBForAllJestWorkers()) { 32 | expect(count).toBe(1); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/mongo-parallelism2.test.ts: -------------------------------------------------------------------------------- 1 | import {MongoClient} from 'mongodb'; 2 | import type {Db} from 'mongodb'; 3 | import '../src/types'; 4 | import {shouldUseSharedDBForAllJestWorkers} from '../src/helpers'; 5 | 6 | describe('parallelism: second worker', () => { 7 | const uri = global.__MONGO_URI__; 8 | let connection: MongoClient; 9 | let db: Db; 10 | 11 | beforeAll(async () => { 12 | // @ts-ignore 13 | connection = await MongoClient.connect(uri, { 14 | // @ts-ignore 15 | useNewUrlParser: true, 16 | useUnifiedTopology: true, 17 | }); 18 | db = await connection.db(); 19 | }); 20 | 21 | afterAll(async () => { 22 | await connection.close(); 23 | }); 24 | 25 | it('should have separate database', async () => { 26 | const collection = db.collection('parallelism-test'); 27 | 28 | await collection.insertMany([{a: 1}, {b: 2}]); 29 | const count = await collection.count({}); 30 | 31 | if (!shouldUseSharedDBForAllJestWorkers()) { 32 | expect(count).toBe(2); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/mongo-parallelism3.test.ts: -------------------------------------------------------------------------------- 1 | import {MongoClient} from 'mongodb'; 2 | import type {Db} from 'mongodb'; 3 | import '../src/types'; 4 | import {shouldUseSharedDBForAllJestWorkers} from '../src/helpers'; 5 | 6 | describe('parallelism: third worker', () => { 7 | const uri = global.__MONGO_URI__; 8 | let connection: MongoClient; 9 | let db: Db; 10 | 11 | beforeAll(async () => { 12 | // @ts-ignore 13 | connection = await MongoClient.connect(uri, { 14 | // @ts-ignore 15 | useNewUrlParser: true, 16 | useUnifiedTopology: true, 17 | }); 18 | db = await connection.db(); 19 | }); 20 | 21 | afterAll(async () => { 22 | await connection.close(); 23 | }); 24 | 25 | it('should have separate database', async () => { 26 | const collection = db.collection('parallelism-test'); 27 | 28 | await collection.insertMany([{a: 1}, {b: 2}, {c: 3}]); 29 | const count = await collection.count({}); 30 | 31 | if (!shouldUseSharedDBForAllJestWorkers()) { 32 | expect(count).toBe(3); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@shelf/tsconfig/backend", 3 | "compilerOptions": { 4 | "strict": true 5 | }, 6 | "exclude": ["node_modules"], 7 | "include": ["src"] 8 | } 9 | --------------------------------------------------------------------------------