├── .circleci └── config.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── lib ├── __tests__ │ └── index.test.ts └── index.ts ├── package-lock.json ├── package.json ├── tsconfig.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | test: 5 | docker: &docker 6 | - image: cimg/node:16.15 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | keys: 11 | - dependency-cache-{{ checksum "package.json" }} 12 | - dependency-cache- 13 | - run: npm install 14 | - save_cache: 15 | key: dependency-cache-{{ checksum "package.json" }} 16 | paths: 17 | - ./node_modules 18 | - run: npm run coverage 19 | deploy: 20 | docker: *docker 21 | steps: 22 | - checkout 23 | - restore_cache: 24 | keys: 25 | - dependency-cache-{{ checksum "package.json" }} 26 | - dependency-cache- 27 | - run: npm install 28 | - save_cache: 29 | key: dependency-cache-{{ checksum "package.json" }} 30 | paths: 31 | - ./node_modules 32 | - run: npm run build 33 | - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 34 | - run: npm publish 35 | 36 | workflows: 37 | commit: 38 | jobs: 39 | - test: 40 | filters: 41 | tags: 42 | only: /.*/ 43 | - deploy: 44 | requires: 45 | - test 46 | filters: 47 | tags: 48 | only: /^v.*/ 49 | branches: 50 | ignore: /.*/ 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,node,visualstudiocode 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # parcel-bundler cache (https://parceljs.org/) 64 | .cache 65 | 66 | # next.js build output 67 | .next 68 | 69 | # nuxt.js build output 70 | .nuxt 71 | 72 | # vuepress build output 73 | .vuepress/dist 74 | 75 | # Serverless directories 76 | .serverless 77 | 78 | # Build directory 79 | dist/ 80 | 81 | ### OSX ### 82 | # General 83 | .DS_Store 84 | .AppleDouble 85 | .LSOverride 86 | 87 | # Icon must end with two \r 88 | Icon 89 | 90 | # Thumbnails 91 | ._* 92 | 93 | # Files that might appear in the root of a volume 94 | .DocumentRevisions-V100 95 | .fseventsd 96 | .Spotlight-V100 97 | .TemporaryItems 98 | .Trashes 99 | .VolumeIcon.icns 100 | .com.apple.timemachine.donotpresent 101 | 102 | # Directories potentially created on remote AFP share 103 | .AppleDB 104 | .AppleDesktop 105 | Network Trash Folder 106 | Temporary Items 107 | .apdisk 108 | 109 | ### VisualStudioCode ### 110 | .vscode/* 111 | !.vscode/settings.json 112 | !.vscode/tasks.json 113 | !.vscode/launch.json 114 | !.vscode/extensions.json 115 | 116 | 117 | # End of https://www.gitignore.io/api/osx,node,visualstudiocode 118 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .circleci/ 2 | coverage/ 3 | lib/ 4 | 5 | tsconfig.json 6 | tslint.json 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Eric Rodrigues Pires 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jest-mock-process [![npm version](https://badge.fury.io/js/jest-mock-process.svg)](https://www.npmjs.com/package/jest-mock-process) [![CircleCI](https://circleci.com/gh/EpicEric/jest-mock-process/tree/main.svg?style=svg)](https://circleci.com/gh/EpicEric/jest-mock-process/tree/main) [![Coverage Status](https://coveralls.io/repos/github/EpicEric/jest-mock-process/badge.svg?branch=main)](https://coveralls.io/github/EpicEric/jest-mock-process?branch=master) 2 | 3 | Easily mock NodeJS process properties in Jest. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | npm install --save-dev jest-mock-process 9 | ``` 10 | 11 | ## Usage 12 | 13 | TypeScript example. 14 | 15 | ```typescript 16 | import { 17 | mockProcessExit, 18 | mockProcessStdout, 19 | mockProcessStderr, 20 | mockProcessUptime, 21 | mockConsoleLog, 22 | } from "jest-mock-process"; 23 | 24 | let mockExit = mockProcessExit(); 25 | process.exit(1); 26 | expect(mockExit).toHaveBeenCalledWith(1); 27 | mockExit = mockProcessExit(new Error("Mock")); 28 | expect(() => process.exit(0)).toThrowError("Mock"); 29 | 30 | const mockStdout = mockProcessStdout(); 31 | process.stdout.write("Hello, world!"); 32 | expect(mockStdout).toHaveBeenCalledWith("Hello, world!"); 33 | 34 | const mockStderr = mockProcessStderr(); 35 | process.stderr.write("Error"); 36 | expect(mockStderr).toHaveBeenCalledWith("Error"); 37 | 38 | const mockUptime = mockProcessUptime(3.14159); 39 | const uptimeValue = process.uptime(); 40 | expect(uptimeValue).toEqual(3.14159); 41 | 42 | const mockLog = mockConsoleLog(); 43 | console.log("Browser log"); 44 | expect(mockLog).toHaveBeenCalledWith("Browser log"); 45 | 46 | mockExit.mockRestore(); 47 | mockStdout.mockRestore(); 48 | mockStderr.mockRestore(); 49 | mockUptime.mockRestore(); 50 | mockLog.mockRestore(); 51 | ``` 52 | 53 | ### Advanced usage 54 | 55 | - You can use `mockedRun` (or `asyncMockedRun`) to set-up a virtual environment that will automatically create and restore provided mocks: 56 | 57 | ```typescript 58 | import { mockedRun, MockedRunResult } from "jest-mock-process"; 59 | 60 | const mockRun = mockedRun({ 61 | stdout: mockProcessStdout, 62 | stderr: mockProcessStderr, 63 | exit: mockProcessExit, 64 | log: mockConsoleLog, 65 | }); 66 | const mockEnvironment = mockRun(() => { 67 | process.stdout.write("stdout payload"); 68 | process.stderr.write("stderr payload"); 69 | process.exit(-1); 70 | console.log("log payload"); 71 | return 10; 72 | }); 73 | expect(mockEnvironment.result).toEqual(10); 74 | expect(mockEnvironment.error).toBeUndefined(); 75 | expect(mockEnvironment.mocks.stdout).toHaveBeenCalledTimes(1); 76 | expect(mockEnvironment.mocks.log).toHaveBeenCalledWith("log payload"); 77 | ``` 78 | 79 | **NOTE: The above is a breaking change in version 2.0.0, as the provided mocks are now limited to the `mocks` object.** 80 | 81 | - You can mock generic methods not supported by default in `jest-mock-process` with the `spyOnImplementing` function: 82 | 83 | ```typescript 84 | import { spyOnImplementing } from "jest-mock-process"; 85 | 86 | const mockStdin = spyOnImplementing(process.stdin, "read", () => ""); 87 | process.stdin.read(1024); 88 | expect(mockStdin).toHaveBeenCalledWith(1024); 89 | ``` 90 | -------------------------------------------------------------------------------- /lib/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | asyncMockedRun, 3 | mockConsoleLog, 4 | mockedRun, 5 | mockProcessExit, 6 | mockProcessStderr, 7 | mockProcessStdout, 8 | mockProcessUptime, 9 | } from "../index"; 10 | 11 | describe("Mock Process Exit", () => { 12 | let mockExit: jest.SpyInstance; 13 | 14 | beforeEach(() => { 15 | mockExit = mockProcessExit(); 16 | }); 17 | 18 | it("should exit with a zero code", () => { 19 | process.exit(0); 20 | expect(mockExit).toHaveBeenCalledTimes(1); 21 | expect(mockExit).toHaveBeenCalledWith(0); 22 | }); 23 | 24 | it("should exit with a non-zero code", () => { 25 | process.exit(-2); 26 | expect(mockExit).toHaveBeenCalledTimes(1); 27 | expect(mockExit).toHaveBeenCalledWith(-2); 28 | }); 29 | 30 | it("should be clearable", () => { 31 | expect(mockExit).toHaveBeenCalledTimes(0); 32 | process.exit(0); 33 | expect(mockExit).toHaveBeenCalledTimes(1); 34 | process.exit(0); 35 | expect(mockExit).toHaveBeenCalledTimes(2); 36 | mockExit.mockClear(); 37 | expect(mockExit).toHaveBeenCalledTimes(0); 38 | }); 39 | 40 | it("should allow an arbitrary error to be thrown on exit site", () => { 41 | const err = new Error("Mock"); 42 | mockExit = mockProcessExit(err); 43 | expect(() => process.exit(0)).toThrowError(err); 44 | }); 45 | 46 | it("should not throw a falsy arbitrary error", () => { 47 | const err = 0; 48 | mockExit = mockProcessExit(err); 49 | expect(() => process.exit(0)).not.toThrow(); 50 | }); 51 | 52 | afterAll(() => { 53 | mockExit.mockRestore(); 54 | }); 55 | }); 56 | 57 | describe("Mock Process Stdout", () => { 58 | let mockStdout: jest.SpyInstance; 59 | 60 | beforeEach(() => { 61 | mockStdout = mockProcessStdout(); 62 | }); 63 | 64 | it("should receive a string", () => { 65 | process.stdout.write("Hello, world!"); 66 | expect(mockStdout).toHaveBeenCalledTimes(1); 67 | expect(mockStdout).toHaveBeenCalledWith("Hello, world!"); 68 | expect(mockStdout).toReturnWith(true); 69 | }); 70 | 71 | it("should receive a buffer", () => { 72 | const buf = Buffer.from("Hello, world"); 73 | process.stdout.write(buf); 74 | expect(mockStdout).toHaveBeenCalledTimes(1); 75 | expect(mockStdout).toHaveBeenCalledWith(buf); 76 | expect(mockStdout).toReturnWith(true); 77 | }); 78 | 79 | it("should receive an encoding", () => { 80 | process.stdout.write("Hello, world!", "utf-8"); 81 | expect(mockStdout).toHaveBeenCalledTimes(1); 82 | expect(mockStdout).toHaveBeenCalledWith("Hello, world!", "utf-8"); 83 | expect(mockStdout).toReturnWith(true); 84 | }); 85 | 86 | it("should receive a callback", () => { 87 | const cb = jest.fn(); 88 | process.stdout.write("", cb); 89 | expect(mockStdout).toHaveBeenCalledTimes(1); 90 | expect(mockStdout).toHaveBeenCalledWith(expect.anything(), cb); 91 | expect(mockStdout).toReturnWith(true); 92 | }); 93 | 94 | it("should be clearable", () => { 95 | expect(mockStdout).toHaveBeenCalledTimes(0); 96 | process.stdout.write(""); 97 | expect(mockStdout).toHaveBeenCalledTimes(1); 98 | process.stdout.write(""); 99 | expect(mockStdout).toHaveBeenCalledTimes(2); 100 | mockStdout.mockClear(); 101 | expect(mockStdout).toHaveBeenCalledTimes(0); 102 | }); 103 | 104 | afterAll(() => { 105 | mockStdout.mockRestore(); 106 | }); 107 | }); 108 | 109 | describe("Mock Process Stderr", () => { 110 | let mockStderr: jest.SpyInstance; 111 | 112 | beforeEach(() => { 113 | mockStderr = mockProcessStderr(); 114 | }); 115 | 116 | it("should receive a string", () => { 117 | process.stderr.write("Hello, world!"); 118 | expect(mockStderr).toHaveBeenCalledTimes(1); 119 | expect(mockStderr).toHaveBeenCalledWith("Hello, world!"); 120 | expect(mockStderr).toReturnWith(true); 121 | }); 122 | 123 | it("should receive a buffer", () => { 124 | const buf = Buffer.from("Hello, world"); 125 | process.stderr.write(buf); 126 | expect(mockStderr).toHaveBeenCalledTimes(1); 127 | expect(mockStderr).toHaveBeenCalledWith(buf); 128 | expect(mockStderr).toReturnWith(true); 129 | }); 130 | 131 | it("should receive an encoding", () => { 132 | process.stderr.write("Hello, world!", "utf-8"); 133 | expect(mockStderr).toHaveBeenCalledTimes(1); 134 | expect(mockStderr).toHaveBeenCalledWith("Hello, world!", "utf-8"); 135 | expect(mockStderr).toReturnWith(true); 136 | }); 137 | 138 | it("should receive a callback", () => { 139 | const cb = jest.fn(); 140 | process.stderr.write("", cb); 141 | expect(mockStderr).toHaveBeenCalledTimes(1); 142 | expect(mockStderr).toHaveBeenCalledWith(expect.anything(), cb); 143 | expect(mockStderr).toReturnWith(true); 144 | }); 145 | 146 | it("should be clearable", () => { 147 | expect(mockStderr).toHaveBeenCalledTimes(0); 148 | process.stderr.write(""); 149 | expect(mockStderr).toHaveBeenCalledTimes(1); 150 | process.stderr.write(""); 151 | expect(mockStderr).toHaveBeenCalledTimes(2); 152 | mockStderr.mockClear(); 153 | expect(mockStderr).toHaveBeenCalledTimes(0); 154 | }); 155 | 156 | afterAll(() => { 157 | mockStderr.mockRestore(); 158 | }); 159 | }); 160 | 161 | describe("Mock Process Uptime", () => { 162 | let mockUptime: jest.SpyInstance; 163 | 164 | beforeEach(() => { 165 | mockUptime = mockProcessUptime(); 166 | }); 167 | 168 | it("should return a default zero value", () => { 169 | expect(process.uptime()).toEqual(0); 170 | expect(mockUptime).toHaveBeenCalledTimes(1); 171 | expect(mockUptime.mock.results[0].value).toEqual(0); 172 | }); 173 | 174 | it("should allow an arbitrary value to be returned", () => { 175 | const value = 3.14159; 176 | mockUptime = mockProcessUptime(value); 177 | expect(process.uptime()).toEqual(value); 178 | expect(mockUptime).toHaveBeenCalledTimes(1); 179 | expect(mockUptime.mock.results[0].value).toEqual(value); 180 | }); 181 | 182 | it("should be clearable", () => { 183 | expect(mockUptime).toHaveBeenCalledTimes(0); 184 | process.uptime(); 185 | expect(mockUptime).toHaveBeenCalledTimes(1); 186 | process.uptime(); 187 | expect(mockUptime).toHaveBeenCalledTimes(2); 188 | mockUptime.mockClear(); 189 | expect(mockUptime).toHaveBeenCalledTimes(0); 190 | }); 191 | 192 | afterAll(() => { 193 | mockUptime.mockRestore(); 194 | }); 195 | }); 196 | 197 | describe("Mock Console Log", () => { 198 | let mockLog: jest.SpyInstance; 199 | 200 | beforeEach(() => { 201 | mockLog = mockConsoleLog(); 202 | }); 203 | 204 | it("should receive a string", () => { 205 | console.log("Hello, world!"); 206 | expect(mockLog).toHaveBeenCalledTimes(1); 207 | expect(mockLog).toHaveBeenCalledWith("Hello, world!"); 208 | }); 209 | 210 | it("should receive an object", () => { 211 | const obj = { array: [] as any[], null: null as any }; 212 | console.log(obj); 213 | expect(mockLog).toHaveBeenCalledTimes(1); 214 | expect(mockLog).toHaveBeenCalledWith(obj); 215 | }); 216 | 217 | it("should be clearable", () => { 218 | expect(mockLog).toHaveBeenCalledTimes(0); 219 | console.log(""); 220 | expect(mockLog).toHaveBeenCalledTimes(1); 221 | console.log(""); 222 | expect(mockLog).toHaveBeenCalledTimes(2); 223 | mockLog.mockClear(); 224 | expect(mockLog).toHaveBeenCalledTimes(0); 225 | }); 226 | 227 | afterAll(() => { 228 | mockLog.mockRestore(); 229 | }); 230 | }); 231 | 232 | describe("mockedRun", () => { 233 | const mockRun = mockedRun({ 234 | stdout: mockProcessStdout, 235 | stderr: mockProcessStderr, 236 | exit: mockProcessExit, 237 | log: mockConsoleLog, 238 | }); 239 | 240 | it("should call every mock once", () => { 241 | let mockEnvironment = mockRun(() => { 242 | process.stdout.write("stdout payload"); 243 | process.stderr.write("stderr payload"); 244 | process.exit(-1); 245 | console.log("log payload"); 246 | }); 247 | expect(mockEnvironment.mocks.stdout).toHaveBeenCalledTimes(1); 248 | expect(mockEnvironment.mocks.stderr).toHaveBeenCalledTimes(1); 249 | expect(mockEnvironment.mocks.exit).toHaveBeenCalledTimes(1); 250 | expect(mockEnvironment.mocks.log).toHaveBeenCalledTimes(1); 251 | }); 252 | 253 | it("should receive the correct arguments", () => { 254 | let mockEnvironment = mockRun(() => { 255 | process.stdout.write("stdout payload"); 256 | process.stderr.write("stderr payload"); 257 | process.exit(-1); 258 | console.log("log payload"); 259 | }); 260 | expect(mockEnvironment.mocks.stdout).toHaveBeenCalledWith("stdout payload"); 261 | expect(mockEnvironment.mocks.stderr).toHaveBeenCalledWith("stderr payload"); 262 | expect(mockEnvironment.mocks.exit).toHaveBeenCalledWith(-1); 263 | expect(mockEnvironment.mocks.log).toHaveBeenCalledWith("log payload"); 264 | }); 265 | 266 | it("should receive the correct return value", () => { 267 | let mockEnvironment = mockRun(() => { 268 | return "return string"; 269 | }); 270 | expect(mockEnvironment.result).toEqual("return string"); 271 | expect(mockEnvironment.error).toBeUndefined(); 272 | }); 273 | 274 | it("should receive the correct thrown value", () => { 275 | const expectedError = new Error("Mock error"); 276 | let mockEnvironment = mockRun(() => { 277 | throw expectedError; 278 | }); 279 | expect(mockEnvironment.result).toBeUndefined(); 280 | expect(mockEnvironment.error).toBe(expectedError); 281 | }); 282 | 283 | it("should accept mocked process.exit raising an error", () => { 284 | const expectedError = new Error("Mock process exit"); 285 | const mockRunWithProcessExit = mockedRun({ 286 | stdout: mockProcessStdout, 287 | stderr: mockProcessStderr, 288 | exit: () => mockProcessExit(expectedError), 289 | log: mockConsoleLog, 290 | }); 291 | let mockEnvironment = mockRunWithProcessExit(() => { 292 | process.stdout.write("stdout payload"); 293 | process.stderr.write("stderr payload"); 294 | process.exit(-1); 295 | console.log("log payload"); 296 | }); 297 | expect(mockEnvironment.mocks.stdout).toHaveBeenCalledTimes(1); 298 | expect(mockEnvironment.mocks.stderr).toHaveBeenCalledTimes(1); 299 | expect(mockEnvironment.mocks.exit).toHaveBeenCalledTimes(1); 300 | expect(mockEnvironment.mocks.log).not.toHaveBeenCalled(); 301 | expect(mockEnvironment.result).toBeUndefined(); 302 | expect(mockEnvironment.error).toBe(expectedError); 303 | }); 304 | }); 305 | 306 | describe("asyncMockedRun", () => { 307 | const mockRun = asyncMockedRun({ 308 | stdout: mockProcessStdout, 309 | stderr: mockProcessStderr, 310 | exit: mockProcessExit, 311 | log: mockConsoleLog, 312 | }); 313 | 314 | it("should call every mock once", async () => { 315 | let mockEnvironment = await mockRun(() => { 316 | return new Promise((resolve) => { 317 | process.stdout.write("stdout payload"); 318 | process.stderr.write("stderr payload"); 319 | process.exit(-1); 320 | console.log("log payload"); 321 | resolve(); 322 | }); 323 | }); 324 | expect(mockEnvironment.mocks.stdout).toHaveBeenCalledTimes(1); 325 | expect(mockEnvironment.mocks.stderr).toHaveBeenCalledTimes(1); 326 | expect(mockEnvironment.mocks.exit).toHaveBeenCalledTimes(1); 327 | expect(mockEnvironment.mocks.log).toHaveBeenCalledTimes(1); 328 | }); 329 | 330 | it("should receive the correct arguments", async () => { 331 | let mockEnvironment = await mockRun(() => { 332 | return new Promise((resolve) => { 333 | process.stdout.write("stdout payload"); 334 | process.stderr.write("stderr payload"); 335 | process.exit(-1); 336 | console.log("log payload"); 337 | resolve(); 338 | }); 339 | }); 340 | expect(mockEnvironment.mocks.stdout).toHaveBeenCalledWith("stdout payload"); 341 | expect(mockEnvironment.mocks.stderr).toHaveBeenCalledWith("stderr payload"); 342 | expect(mockEnvironment.mocks.exit).toHaveBeenCalledWith(-1); 343 | expect(mockEnvironment.mocks.log).toHaveBeenCalledWith("log payload"); 344 | }); 345 | 346 | it("should receive the correct return value", async () => { 347 | let mockEnvironment = await mockRun(() => { 348 | return new Promise((resolve) => { 349 | resolve("return string"); 350 | }); 351 | }); 352 | expect(mockEnvironment.result).toEqual("return string"); 353 | expect(mockEnvironment.error).toBeUndefined(); 354 | }); 355 | 356 | it("should receive the correct thrown value", async () => { 357 | const expectedError = new Error("Mock error"); 358 | let mockEnvironment = await mockRun(() => { 359 | return new Promise((_, reject) => { 360 | reject(expectedError); 361 | }); 362 | }); 363 | expect(mockEnvironment.result).toBeUndefined(); 364 | expect(mockEnvironment.error).toBe(expectedError); 365 | }); 366 | 367 | it("should accept mocked process.exit raising an error", async () => { 368 | const expectedError = new Error("Mock process exit"); 369 | const mockRunWithProcessExit = asyncMockedRun({ 370 | stdout: mockProcessStdout, 371 | stderr: mockProcessStderr, 372 | exit: () => mockProcessExit(expectedError), 373 | log: mockConsoleLog, 374 | }); 375 | let mockEnvironment = await mockRunWithProcessExit(() => { 376 | return new Promise((resolve) => { 377 | process.stdout.write("stdout payload"); 378 | process.stderr.write("stderr payload"); 379 | process.exit(-1); 380 | console.log("log payload"); 381 | resolve(); 382 | }); 383 | }); 384 | expect(mockEnvironment.mocks.stdout).toHaveBeenCalledTimes(1); 385 | expect(mockEnvironment.mocks.stderr).toHaveBeenCalledTimes(1); 386 | expect(mockEnvironment.mocks.exit).toHaveBeenCalledTimes(1); 387 | expect(mockEnvironment.mocks.log).not.toHaveBeenCalled(); 388 | expect(mockEnvironment.result).toBeUndefined(); 389 | expect(mockEnvironment.error).toBe(expectedError); 390 | }); 391 | }); 392 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | const maybeMockRestore = (a: any): void => 2 | a.mockRestore && typeof a.mockRestore === "function" 3 | ? a.mockRestore() 4 | : undefined; 5 | 6 | type JMPFunctionPropertyNames = { 7 | [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; 8 | }[keyof T] & 9 | string; 10 | type JMPArgsType = T extends (...args: infer A) => any ? A : never; 11 | type JMPReturnType = T extends (...args: any[]) => infer A ? A : never; 12 | 13 | /** 14 | * Helper function for manually creating new spy mocks of functions not supported by this module. 15 | * 16 | * @param target Object containing the function that will be mocked. 17 | * @param property Name of the function that will be mocked. 18 | * @param impl Mock implementation of the target's function. The return type must match the target function's. 19 | */ 20 | export function spyOnImplementing< 21 | T extends {}, 22 | M extends JMPFunctionPropertyNames, 23 | F extends T[M], 24 | I extends (...args: any[]) => JMPReturnType 25 | >( 26 | target: T, 27 | property: M, 28 | impl: I 29 | ): jest.SpyInstance>, JMPArgsType> { 30 | maybeMockRestore(target[property]); 31 | return jest.spyOn(target, property as any).mockImplementation(impl); 32 | } 33 | 34 | /** 35 | * Helper function to create a mock of the Node.js method 36 | * `process.exit(code: number)`. 37 | * 38 | * @param {Object} err Optional error to raise. If unspecified or falsy, calling `process.exit` will resume code 39 | * execution instead of raising an error. 40 | */ 41 | export const mockProcessExit = (err?: any) => 42 | spyOnImplementing( 43 | process, 44 | "exit", 45 | (err 46 | ? () => { 47 | throw err; 48 | } 49 | : () => {}) as () => never 50 | ); 51 | 52 | /** 53 | * Helper function to create a mock of the Node.js method 54 | * `process.stdout.write(text: string, callback?: function): boolean`. 55 | */ 56 | export const mockProcessStdout = () => 57 | spyOnImplementing(process.stdout, "write", () => true); 58 | 59 | /** 60 | * Helper function to create a mock of the Node.js method 61 | * `process.stderr.write(text: string, callback?: function): boolean`. 62 | */ 63 | export const mockProcessStderr = () => 64 | spyOnImplementing(process.stderr, "write", () => true); 65 | 66 | /** 67 | * Helper function to create a mock of the Node.js method 68 | * `process.uptime()`. 69 | */ 70 | export const mockProcessUptime = (value?: number) => 71 | spyOnImplementing(process, "uptime", () => value ?? 0); 72 | 73 | /** 74 | * Helper function to create a mock of the Node.js method 75 | * `console.log(message: any)`. 76 | */ 77 | export const mockConsoleLog = () => spyOnImplementing(console, "log", () => {}); 78 | 79 | type JestCallableMocksObject = { 80 | [_: string]: () => jest.SpyInstance; 81 | }; 82 | 83 | type JestMocksObject = { 84 | [K in keyof T]: T[K] extends () => infer J ? J : never; 85 | }; 86 | 87 | export interface MockedRunResult { 88 | error?: any; 89 | result?: R; 90 | mocks: M; 91 | } 92 | 93 | /** 94 | * Helper function to run a synchronous function with provided mocks in place, as a virtual environment. 95 | * 96 | * Every provided mock will be automatically restored when this function returns. 97 | */ 98 | export function mockedRun(callers: T) { 99 | return (f: () => R) => { 100 | const mocks: any = { 101 | mocks: {}, 102 | }; 103 | const mockers: { [_: string]: jest.SpyInstance } = Object.entries(callers) 104 | .map(([k, caller]) => ({ [k]: caller() })) 105 | .reduce((o, acc) => Object.assign(acc, o), {}); 106 | 107 | try { 108 | mocks.result = f(); 109 | } catch (error) { 110 | mocks.error = error; 111 | } 112 | 113 | Object.entries(mockers).map(([k, mocker]) => { 114 | mocks.mocks[k] = Object.assign({}, mocker); 115 | maybeMockRestore(mocker); 116 | }); 117 | 118 | return mocks as MockedRunResult>; 119 | }; 120 | } 121 | 122 | /** 123 | * Helper function to run an asynchronous function with provided mocks in place, as a virtual environment. 124 | * 125 | * Every provided mock will be automatically restored when this function returns. 126 | */ 127 | export function asyncMockedRun( 128 | callers: T 129 | ) { 130 | return async (f: () => Promise) => { 131 | const mocks: any = { 132 | mocks: {}, 133 | }; 134 | const mockers: { [_: string]: jest.SpyInstance } = Object.entries(callers) 135 | .map(([k, caller]) => ({ [k]: caller() })) 136 | .reduce((o, acc) => Object.assign(acc, o), {}); 137 | 138 | try { 139 | mocks.result = await f(); 140 | } catch (error) { 141 | mocks.error = error; 142 | } 143 | 144 | Object.entries(mockers).map(([k, mocker]) => { 145 | mocks.mocks[k] = Object.assign({}, mocker); 146 | maybeMockRestore(mocker); 147 | }); 148 | 149 | return mocks as MockedRunResult>; 150 | }; 151 | } 152 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-mock-process", 3 | "version": "2.0.0", 4 | "description": "Easily mock NodeJS process properties in Jest", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "coverage": "jest --coverage --coverageReporters=text-lcov | coveralls", 10 | "postversion": "git push --follow-tags", 11 | "test": "jest --coverage", 12 | "test:watch": "jest --coverage --watchAll" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/EpicEric/jest-mock-process.git" 17 | }, 18 | "keywords": [ 19 | "jest", 20 | "mock", 21 | "node", 22 | "process", 23 | "exit", 24 | "stdout", 25 | "stderr" 26 | ], 27 | "author": "Eric Rodrigues Pires ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/EpicEric/jest-mock-process/issues" 31 | }, 32 | "homepage": "https://github.com/EpicEric/jest-mock-process#readme", 33 | "devDependencies": { 34 | "@types/jest": "^27.5.2", 35 | "@types/node": "^10.17.28", 36 | "coveralls": "^3.1.1", 37 | "jest": "^28.1.0", 38 | "ts-jest": "^28.0.3", 39 | "typescript": "4.5" 40 | }, 41 | "peerDependencies": { 42 | "jest": ">=23.4" 43 | }, 44 | "jest": { 45 | "moduleFileExtensions": [ 46 | "ts", 47 | "tsx", 48 | "js", 49 | "jsx", 50 | "json", 51 | "node" 52 | ], 53 | "transform": { 54 | "^.+\\.tsx?$": "ts-jest" 55 | }, 56 | "testEnvironment": "node", 57 | "testMatch": [ 58 | "**/__tests__/**/*.test.(ts|tsx)" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "dist", 7 | "strict": true, 8 | "strictNullChecks": false, 9 | "strictPropertyInitialization": false, 10 | "types": [ 11 | "node", 12 | "jest" 13 | ], 14 | "esModuleInterop": true, 15 | "preserveSymlinks": true 16 | }, 17 | "include": [ 18 | "lib/*.ts", 19 | "lib/!(__tests__)/**/*.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended" 4 | ], 5 | "rules": { 6 | "quotemark": [ 7 | true, 8 | "single", 9 | "jsx-single" 10 | ], 11 | "trailing-comma": false, 12 | "indent": [true, "spaces", 4], 13 | "no-submodule-imports": false, 14 | "no-empty": false, 15 | "member-ordering": false, 16 | "no-implicit-dependencies": [true, "dev"], 17 | "no-var-requires": false, 18 | "object-literal-sort-keys": false, 19 | "interface-name": false, 20 | "no-console": false 21 | } 22 | } 23 | --------------------------------------------------------------------------------