├── .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 [](https://circleci.com/gh/shelfio/jest-mongodb/tree/master)  [](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 |
--------------------------------------------------------------------------------