├── .eslintrc
├── .github
└── workflows
│ ├── node.js.yml
│ └── stale.yml
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .mocharc.js
├── .npmrc
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CODING_RULES.md
├── CONTRIBUTING.md
├── HISTORY.md
├── LICENSE
├── README.md
├── examples
├── express-route.js
└── express-status-vs-json.js
├── lib
├── express
│ ├── mock-application.js
│ ├── mock-express.js
│ ├── mock-request.js
│ └── utils
│ │ └── define-getter.js
├── headers.js
├── http-mock.d.ts
├── http-mock.js
├── mockEventEmitter.js
├── mockRequest.js
├── mockResponse.js
├── mockWritableStream.js
├── node
│ ├── _http_incoming.js
│ ├── _http_server.js
│ └── http.js
└── utils.js
├── package-lock.json
├── package.json
├── test
└── lib
│ ├── headers.spec.js
│ ├── http-mock.test-d.ts
│ ├── mockEventEmitter.spec.js
│ ├── mockExpressResponse.spec.js
│ ├── mockRequest.spec.ts
│ ├── mockResponse.spec.js
│ ├── mockWritableStream.spec.js
│ └── node-http-mock.spec.js
└── tsconfig.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "airbnb-base",
4 | "prettier"
5 | ],
6 | "parser": "@typescript-eslint/parser",
7 | "rules": {
8 | "no-underscore-dangle": "off",
9 | "no-console": "off",
10 | "no-plusplus": "off",
11 | "func-names": [
12 | "warn",
13 | "as-needed"
14 | ],
15 | "prefer-destructuring": [
16 | "error",
17 | {
18 | "object": true,
19 | "array": false
20 | }
21 | ],
22 | "no-prototype-builtins": "warn",
23 | "no-restricted-syntax": [
24 | "error",
25 | {
26 | "selector": "LabeledStatement",
27 | "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand."
28 | },
29 | {
30 | "selector": "WithStatement",
31 | "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize."
32 | }
33 | ]
34 | },
35 | "overrides": [
36 | {
37 | "files": [
38 | "test/**/*.js",
39 | "test/**/*.ts"
40 | ],
41 | "rules": {
42 | "no-unused-expressions": "off"
43 | },
44 | "env": {
45 | "mocha": true
46 | }
47 | }
48 | ],
49 | "root": true
50 | }
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [16.x, 18.x, 20.x, 22.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm run build --if-present
31 | - run: npm run check
32 | - run: npm run coverage
33 | # - name: Code Coverage Report
34 | # uses: romeovs/lcov-reporter-action@v0.3.1
35 | # if: ${{ matrix.node-version == '20.x' }}
36 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues and pull requests
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * *"
6 |
7 | jobs:
8 | stale:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/stale@v1
14 | with:
15 | repo-token: ${{ secrets.GITHUB_TOKEN }}
16 | stale-issue-message: 'Stale issue message'
17 | stale-pr-message: 'Stale pull request message'
18 | stale-issue-label: 'no-issue-activity'
19 | stale-pr-label: 'no-pr-activity'
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | .settings
3 | coverage
4 | node_modules
5 | test/results
6 | npm-debug.log
7 | TAGS
8 | /.idea/
9 | .nyc_output
10 | .DS_Store
11 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm run check
5 |
6 |
--------------------------------------------------------------------------------
/.mocharc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | recursive: true,
5 | reporter: 'spec',
6 | extension: ['js', 'ts'],
7 | require: 'ts-node/register',
8 | ui: 'bdd'
9 | };
10 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /.github/
2 | /.vscode/
3 | /.idea/
4 | .eslintrc
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "trailingComma": "none",
4 | "singleQuote": true,
5 | "tabWidth": 4,
6 | "overrides": [
7 | {
8 | "files": ["package.json", "package-lock.json"],
9 | "options": {
10 | "tabWidth": 2
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4 |
5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6 |
7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8 |
9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10 |
11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12 |
13 | This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
14 |
--------------------------------------------------------------------------------
/CODING_RULES.md:
--------------------------------------------------------------------------------
1 | # Coding Rules
2 |
3 | For simplicity, we've divided our coding rules using the same categories as the ESLint documentation.
4 |
5 | - [Possible Errors](#errors)
6 | - [Best Practices](#best)
7 | - [Strict Mode](#strict)
8 | - [Variables](#variables)
9 | - [Node.js](#node)
10 | - [Stylistic Issues](#style)
11 |
12 | ## Possible Errors
13 |
14 | The following rules point out areas where you might have made mistakes.
15 |
16 | - [comma-dangle] - enforce trailing commas
17 | - [no-control-regex] - disallow control characters in regular expressions
18 | - [no-debugger] - disallow use of debugger
19 | - [no-dupe-keys] - disallow duplicate keys when creating object literals
20 | - [no-empty] - disallow empty statements
21 | - [no-empty-class] - disallow the use of empty character classes in regular expressions
22 | - [no-ex-assign] - disallow assigning to the exception in a `catch block
23 | - [no-func-assign] - disallow overwriting functions written as function declarations
24 | - [no-unreachable] - disallow unreachable statements after a return, throw, continue, or break statement
25 | - [no-obj-calls] - disallow the use of object properties of the global object (`Math` and `JSON`) as functions
26 | - [no-regex-spaces] - disallow multiple spaces in a regular expression literal
27 | - [use-isnan] - disallow comparisons with the value `NaN`
28 | - [valid-typeof] - Ensure that the results of `typeof` are compared against a valid string
29 |
30 | [Back to Top](#top)
31 |
32 | ## Best Practices
33 |
34 | These are rules designed to prevent you from making mistakes.
35 |
36 | - [no-caller] - disallow use of `arguments.caller` or `arguments.callee`
37 | - [no-div-regex] - disallow division operators explicitly at beginning of regular expression
38 | - [no-else-return] - disallow `else` after a `return` in an `if`
39 | - [no-eq-null] - disallow comparisons to null without a type-checking operator
40 | - [no-eval] - disallow use of `eval()`
41 | - [no-floating-decimal] - disallow the use of leading or trailing decimal points in numeric literals
42 | - [no-implied-eval] - disallow use of `eval()`-like methods
43 | - [no-with] - disallow use of the `with` statement
44 | - [no-fallthrough] - disallow fallthrough of case statements
45 | - [no-unused-expressions] - disallow usage of expressions in statement position
46 | - [no-octal] - disallow use of octal literals
47 | - [no-octal-escape] - disallow use of octal escape sequences in string literals, such as `var foo = "Copyright \251";`
48 | - [no-multi-str] - disallow use of multiline strings
49 | - [no-new-wrappers] - disallows creating new instances of `String`, `Number`, and `Boolean`
50 | - [no-new] - disallow use of new operator when not part of the assignment or comparison
51 | - [no-new-func] - disallow use of new operator for `Function` object
52 | - [no-native-reassign] - disallow reassignments of native objects
53 | - [no-return-assign] - disallow use of assignment in return statement
54 | - [no-self-compare] - disallow comparisons where both sides are exactly the same
55 | - [no-loop-func] - disallow creation of functions within loops
56 | - [no-empty-label] - disallow use of labels for anything other then loops and switches
57 | - [no-script-url] - disallow use of javascript: urls.
58 | - [no-proto] - disallow usage of `__proto__` property
59 | - [no-iterator] - disallow usage of `__iterator__` property
60 | - [no-redeclare] - disallow declaring the same variable more then once
61 | - [curly] - specify curly brace conventions for all control statements
62 | - [dot-notation] - encourages use of dot notation whenever possible
63 | - [eqeqeq] - require the use of `===` and `!==`
64 | - [wrap-iife] - require immediate function invocation to be wrapped in parentheses
65 |
66 | [Back to Top](#top)
67 |
68 | ## Strict Mode
69 |
70 | These rules relate to using strict mode.
71 |
72 | - [strict] - ensures all code is in strict mode and that there are no extraneous Use Strict Directives
73 |
74 | [Back to Top](#top)
75 |
76 | ## Variables
77 |
78 | These rules have to do with variable declarations.
79 |
80 | - [no-catch-shadow] - disallow the catch clause parameter name being the same as a variable in the outer scope
81 | - [no-undef] - disallow use of undeclared variables unless mentioned in a `/*global */` block
82 | - [no-undef-init] - disallow use of undefined when initializing variables
83 | - [no-delete-var] - disallow deletion of variables
84 | - [no-label-var] - disallow labels that share a name with a variable
85 | - [no-unused-vars] - disallow declaration of variables that are not used in the code
86 | - [no-shadow] - disallow declaration of variables already declared in the outer scope
87 | - [no-use-before-define] - disallow use of variables before they are defined
88 |
89 | [Back to Top](#top)
90 |
91 | ## Node.js
92 |
93 | These rules are specific to JavaScript running on Node.js.
94 |
95 | - [no-sync] - disallow use of synchronous methods
96 | - [no-mixed-requires] - allow mixing regular variable and require declarations
97 |
98 | [Back to Top](#top)
99 |
100 | ## Stylistic Issues
101 |
102 | - [no-array-constructor] - disallow use of the `Array` `constructor
103 | - [no-new-object] - disallow use of the `Object constructor`
104 | - [no-wrap-func] - disallow wrapping of non-IIFE statements in parens
105 | - [brace-style] - enforce one true brace style
106 | - [camelcase] - require camel case names
107 | - [consistent-this] - enforces consistent naming when capturing the current execution context
108 | - [new-cap] - require a capital letter for constructors
109 | - [new-parens] - disallow the omission of parentheses when invoking a constructor with no arguments
110 | - [quotes] - specify whether double or single quotes should be used
111 | - [semi] - require use of semicolons instead of ASI
112 | - [no-mixed-spaces-and-tabs] - disallow mixed spaces and tabs for indentation
113 | - [indent] - 4 spaces indentation with enabled switch cases validation
114 |
115 | [Back to Top](#top)
116 |
117 | [comma-dangle]: http://eslint.org/docs/rules/comma-dangle.html
118 | [no-control-regex]: http://eslint.org/docs/rules/no-control-regex.html
119 | [no-debugger]: http://eslint.org/docs/rules/no-debugger.html
120 | [no-dupe-keys]: http://eslint.org/docs/rules/no-dupe-keys.html
121 | [no-empty]: http://eslint.org/docs/rules/no-empty.html
122 | [no-empty-class]: http://eslint.org/docs/rules/no-empty-class.html
123 | [no-ex-assign]: http://eslint.org/docs/rules/no-ex-assign.html
124 | [no-func-assign]: http://eslint.org/docs/rules/no-func-assign.html
125 | [no-unreachable]: http://eslint.org/docs/rules/no-unreachable.html
126 | [no-obj-calls]: http://eslint.org/docs/rules/no-obj-calls.html
127 | [no-regex-spaces]: http://eslint.org/docs/rules/no-regex-spaces.html
128 | [use-isnan]: http://eslint.org/docs/rules/use-isnan.html
129 | [valid-typeof]: http://eslint.org/docs/rules/valid-typeof.html
130 | [no-caller]: http://eslint.org/docs/rules/no-caller.html
131 | [no-div-regex]: http://eslint.org/docs/rules/no-div-regex.html
132 | [no-else-return]: http://eslint.org/docs/rules/no-else-return.html
133 | [no-eq-null]: http://eslint.org/docs/rules/no-eq-null.html
134 | [no-eval]: http://eslint.org/docs/rules/no-eval.html
135 | [no-floating-decimal]: http://eslint.org/docs/rules/no-floating-decimal.html
136 | [no-implied-eval]: http://eslint.org/docs/rules/no-implied-eval.html
137 | [no-with]: http://eslint.org/docs/rules/no-with.html
138 | [no-fallthrough]: http://eslint.org/docs/rules/no-fallthrough.html
139 | [no-unused-expressions]: http://eslint.org/docs/rules/no-unused-expressions.html
140 | [no-octal]: http://eslint.org/docs/rules/no-octal.html
141 | [no-octal-escape]: http://eslint.org/docs/rules/no-octal-escape.html
142 | [no-multi-str]: http://eslint.org/docs/rules/no-multi-str.html
143 | [no-new-wrappers]: http://eslint.org/docs/rules/no-new-wrappers.html
144 | [no-new]: http://eslint.org/docs/rules/no-new.html
145 | [no-new-func]: http://eslint.org/docs/rules/no-new-func.html
146 | [no-native-reassign]: http://eslint.org/docs/rules/no-native-reassign.html
147 | [no-return-assign]: http://eslint.org/docs/rules/no-return-assign.html
148 | [no-self-compare]: http://eslint.org/docs/rules/no-self-compare.html
149 | [no-loop-func]: http://eslint.org/docs/rules/no-loop-func.html
150 | [no-empty-label]: http://eslint.org/docs/rules/no-empty-label.html
151 | [no-script-url]: http://eslint.org/docs/rules/no-script-url.html
152 | [no-proto]: http://eslint.org/docs/rules/no-proto.html
153 | [no-iterator]: http://eslint.org/docs/rules/no-iterator.html
154 | [no-redeclare]: http://eslint.org/docs/rules/no-redeclare.html
155 | [curly]: http://eslint.org/docs/rules/curly.html
156 | [dot-notation]: http://eslint.org/docs/rules/dot-notation.html
157 | [eqeqeq]: http://eslint.org/docs/rules/eqeqeq.html
158 | [wrap-iife]: http://eslint.org/docs/rules/wrap-iife.html
159 | [strict]: http://eslint.org/docs/rules/strict.html
160 | [no-catch-shadow]: http://eslint.org/docs/rules/no-catch-shadow.html
161 | [no-undef]: http://eslint.org/docs/rules/no-undef.html
162 | [no-undef-init]: http://eslint.org/docs/rules/no-undef-init.html
163 | [no-delete-var]: http://eslint.org/docs/rules/no-delete-var.html
164 | [no-label-var]: http://eslint.org/docs/rules/no-label-var.html
165 | [no-unused-vars]: http://eslint.org/docs/rules/no-unused-vars.html
166 | [no-shadow]: http://eslint.org/docs/rules/no-shadow.html
167 | [no-use-before-define]: http://eslint.org/docs/rules/no-use-before-define.html
168 | [no-sync]: http://eslint.org/docs/rules/no-sync.html
169 | [no-mixed-requires]: http://eslint.org/docs/rules/no-mixed-requires.html
170 | [no-array-constructor]: http://eslint.org/docs/rules/no-array-constructor.html
171 | [no-new-object]: http://eslint.org/docs/rules/no-new-object.html
172 | [no-wrap-func]: http://eslint.org/docs/rules/no-wrap-func.html
173 | [brace-style]: http://eslint.org/docs/rules/brace-style.html
174 | [camelcase]: http://eslint.org/docs/rules/camelcase.html
175 | [consistent-this]: http://eslint.org/docs/rules/consistent-this.html
176 | [new-cap]: http://eslint.org/docs/rules/new-cap.html
177 | [new-parens]: http://eslint.org/docs/rules/new-parens.html
178 | [quotes]: http://eslint.org/docs/rules/quotes.html
179 | [semi]: http://eslint.org/docs/rules/semi.html
180 | [no-mixed-spaces-and-tabs]: http://eslint.org/docs/rules/no-mixed-spaces-and-tabs.html
181 | [indent]: http://eslint.org/docs/rules/indent.html
182 | [no-underscore-dangle]: http://eslint.org/docs/rules/no-underscore-dangle.html
183 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | As a community-focused, open source project, contributions are always welcome, no matter how large or small. Here are the guidelines we ask our contributors to follow:
4 |
5 | - [Code of Conduct](#coc)
6 | - [Issues and Bugs](#issue)
7 | - [Feature Requests](#feature)
8 | - [Submission Guidelines](#submit)
9 | - [Coding Rules](#rules)
10 | - [Running Test Suite](#tests)
11 | - [Contact Us](#contact)
12 |
13 | > **NOTE**
14 |
15 | > While we complete our transition to version `2.0`, please adhere to the following:
16 |
17 | - For any contributions to our `2.0` track, please create a topic branch based off our `master` branch, push said topic branch onto your fork and submit your pull request from that branch.
18 | - Anyone wishing to contribute a bug fix to our `1.x` track, please create a topic branch based off our `1.x` branch, push said topic branch onto your fork and submit your pull request from that branch..
19 |
20 | ## Code of Conduct
21 |
22 | We want to keep our project open and inclusive. We ask that before you
23 | contribute, you read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).
24 |
25 | ## Found an Issue?
26 |
27 | We definitely want to hear from you!
28 |
29 | If you find a bug in the source code or a mistake in the docs, you can help us by
30 | submitting an issue to our [Repository][issues]. Make sure you search through our existing [open and closed issues][issues-archive] in order to avoid duplicate submissions.
31 |
32 | Want to contribute with a fix? Even better! Just submit a [Pull Request][pulls].
33 |
34 | **Please read the [Submission Guidelines](#submit) below**.
35 |
36 | ## Want a Feature?
37 |
38 | Need a new feature no yet available on node-mocks-http? Submit a new feature to our [GitHub Repository][issues].
39 |
40 | Think you can help us out by implementing the feature yourself? Go for it! Just craft and submit your [Pull Request][pulls].
41 |
42 | **Please read the [Submission Guidelines](#submit) below**.
43 |
44 | ## Submission Guidelines
45 |
46 | ### Submitting an Issue
47 |
48 | Before you submit your issue search the [archive][issues-archive], maybe your question was already answered. Let's avoid duplicates.
49 |
50 | If you believe your issue is a bug, and you can't find a issue in the [archive][issues-archive], just open a new issue.
51 |
52 | **Help us help you!**
53 |
54 | Provide the following information to help us identify and fix the issue in a timely manner:
55 |
56 | - **Overview** - describe the issue the best way you can, and if possible include a stack trace
57 | - **Use Case** - explain why you consider this a bug
58 | - **Version(s)** - tell us what version of node-mocks-http you're currently using
59 | - **Reproduce** - it would be awesome if you could provide a live example (using [Plunker][plunker] or
60 | [JSFiddle][jsfiddle]), or at least a step-by-step description on how to reproduce it
61 | - **Suggestions** - if you have identified the lines of code or the commit responsible for the problem please include it as well
62 |
63 | ### Submitting a Pull Request
64 |
65 | We are a _Pull Request-friendly_ project!
66 |
67 | Your pull requests are always welcome. We only ask that you adhere to the following guidelines before you submit your pull request:
68 |
69 | - Search [GitHub][pulls] for an open or closed Pull Request that may be similar to yours. Avoid duplicates!
70 | - Fork our [repo][repo] and create a local clone, if you haven't done so already.
71 |
72 | ```shell
73 | git clone https://github.com/YOUR-NAME/node-mocks-http.git
74 | ```
75 |
76 | - If you had previously cloned the [repo][repo], make sure you sync it with the upstream repository.
77 |
78 | ```shell
79 | git remote add upstream https://github.com/eugef/node-mocks-http.git
80 | git fetch upstream
81 | git checkout master
82 | git merge upstream/master
83 | ```
84 |
85 | - Create a new topic branch:
86 |
87 | ```shell
88 | git checkout -b my-awesome-fix master
89 | ```
90 |
91 | - Now do your thing! Create your fix/patch, **including appropriate test cases**.
92 | - Follow our [Coding Rules](#rules).
93 | - Run our test suite, as described in [below](#tests),
94 | and ensure that all tests pass.
95 | - Commit your changes using a descriptive commit message
96 |
97 | ```shell
98 | git commit -a
99 | ```
100 |
101 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
102 |
103 | - Push your branch to GitHub:
104 |
105 | ```shell
106 | git push origin my-awesome-fix
107 | ```
108 |
109 | - In GitHub, send a pull request to `node-mocks-http`.
110 | - If we find any issues we may suggest that you:
111 |
112 | - Make the required updates.
113 | - Re-run the [test suite](#tests) to ensure tests are still passing.
114 | - Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
115 |
116 | ```shell
117 | git rebase master -i
118 | git push origin my-awesome-fix -f
119 | ```
120 |
121 | That's it!
122 |
123 | #### Post merged cleanup
124 |
125 | After we merge your pull request, you can safely delete your branch and pull the changes from our main (upstream) repository:
126 |
127 | - Delete the remote branch on GitHub either through the GitHub web interface or your local shell as follows:
128 |
129 | ```shell
130 | git push origin --delete my-awesome-fix
131 | ```
132 |
133 | - Check out the master branch:
134 |
135 | ```shell
136 | git checkout master -f
137 | ```
138 |
139 | - Delete the local branch:
140 |
141 | ```shell
142 | git branch -D my-awesome-fix
143 | ```
144 |
145 | - Update your master with the latest upstream version:
146 |
147 | ```shell
148 | git pull --ff upstream master
149 | ```
150 |
151 | ## Coding Rules
152 |
153 | For a detailed list our the conding conventions used in our project please read our [Coding Rules](CODING_RULES.md).
154 |
155 | ## Running Test Suite
156 |
157 | Navigate to the project folder and run `npm install` to install the
158 | project's dependencies.
159 |
160 | Then simply run the tests. This also checks that the code adheres to the ESLint rules.
161 |
162 | npm test
163 |
164 | Also, please adhere to the ESLint's rules by running the following:
165 |
166 | npm install -g gulp
167 | gulp lint
168 |
169 | Or:
170 |
171 | npx gulp lint
172 |
173 | Failures in the linting process may fail our continuous integration builds.
174 |
175 | ### TypeScript tests
176 |
177 | It's also possible to implement tests in TypeScript, primarily for the purpose of testing the TypeScript definitions. To run these tests, use:
178 |
179 | npm run test:ts
180 |
181 | Note: at this point ESLint is not ran against TypeScript tests.
182 |
183 | Thanks again for helping out!
184 |
185 | [repo]: https://github.com/eugef/node-mocks-http
186 | [issues]: https://github.com/eugef/node-mocks-http/issues
187 | [issues-archive]: https://github.com/eugef/node-mocks-http/issues?q=is%3Aissue
188 | [pulls]: https://github.com/eugef/node-mocks-http/pulls
189 | [pulls-archive]: https://github.com/eugef/node-mocks-http/pulls?q=is%3Apr
190 | [jsfiddle]: http://jsfiddle.net/
191 | [plunker]: http://plnkr.co/edit
192 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | ## v 1.17.2
2 |
3 | - Fix request.get() when headers are set as an object [Issue #321][321].
4 |
5 | [321]: https://github.com/eugef/node-mocks-http/pull/321
6 |
7 | ## v 1.17.1
8 |
9 | - Fix direct access to the headers [Issue #319][319].
10 |
11 | [319]: https://github.com/eugef/node-mocks-http/pull/319
12 |
13 | ## v 1.17.0
14 |
15 | - Allow access request headers both in Express.js and Web Api (Next.js) manner [Issue #317][317].
16 |
17 | [317]: https://github.com/eugef/node-mocks-http/pull/317
18 |
19 | ## v 1.16.2
20 |
21 | - Fix: writeHead() sets headersSent [Issue #313][313].
22 |
23 | [313]: https://github.com/eugef/node-mocks-http/pull/313
24 |
25 | ## v 1.16.1
26 |
27 | - Allowing @types/express v5 [Issue #309][309].
28 |
29 | [309]: https://github.com/eugef/node-mocks-http/pull/309
30 |
31 | ## v 1.16.0
32 |
33 | - Add appendHeader to MockResponse [Issue #306][306].
34 | - Add Fetch API types as accepted mock parameters [Issue #291][291].
35 |
36 | [306]: https://github.com/eugef/node-mocks-http/pull/306
37 | [291]: https://github.com/eugef/node-mocks-http/pull/291
38 |
39 | ## v 1.15.1
40 |
41 | - Allowing @types/node v22 [Issue #305][305].
42 |
43 | [305]: https://github.com/eugef/node-mocks-http/pull/305
44 |
45 | ## v 1.15.0
46 |
47 | - Add to write() and end() support of TypedArray [Issue #300][300].
48 | - Fix: return empty string when send() was called with undefined [Issue #298][298].
49 |
50 | [300]: https://github.com/eugef/node-mocks-http/pull/300
51 | [298]: https://github.com/eugef/node-mocks-http/pull/298
52 |
53 | ## v 1.14.1
54 |
55 | - Move express and node types to prod deps [Issue #290][290].
56 |
57 | [290]: https://github.com/eugef/node-mocks-http/pull/290
58 |
59 | ## v 1.14.0
60 |
61 | - Fix nodejs typing [Issue #281][281].
62 | - Add testing capability in TypeScript [Issue #282][282].
63 |
64 | [281]: https://github.com/eugef/node-mocks-http/pull/281
65 | [282]: https://github.com/eugef/node-mocks-http/pull/282
66 |
67 | ## v 1.13.0
68 |
69 | - Add Request async iterator [Issue #278][278].
70 |
71 | [278]: https://github.com/eugef/node-mocks-http/issues/278
72 |
73 | ## v 1.12.2
74 |
75 | - 📦️ Upgrade @types/express [Issue #267][267].
76 | - setHeader should return this [Issue #268][268].
77 |
78 | [267]: https://github.com/eugef/node-mocks-http/issues/267
79 | [268]: https://github.com/eugef/node-mocks-http/issues/268
80 |
81 | ## v 1.12.1
82 |
83 | - Fix lint issue
84 |
85 | ## v 1.12.0
86 |
87 | - Make it easier to create mocks for the node http library [Issue #259][259].
88 | - mockResponse.end(): added callback triggering once end() logic has run [Issue #248][248].
89 |
90 | [259]: https://github.com/eugef/node-mocks-http/issues/259
91 | [248]: https://github.com/eugef/node-mocks-http/issues/248
92 |
93 | ## v 1.11.0
94 |
95 | - Fix request.ip and add request.ips [Issue #244][244].
96 | - Add response.attachment() from express [Issue #246][246].
97 | - Add request.getHeader() alias for request.header() [Issue #241][241].
98 |
99 | [244]: https://github.com/eugef/node-mocks-http/issues/244
100 | [246]: https://github.com/eugef/node-mocks-http/issues/246
101 | [241]: https://github.com/eugef/node-mocks-http/issues/241
102 |
103 | ## v 1.10.1
104 |
105 | - Fix support for req.hostname [Issue #231][231].
106 |
107 | [231]: https://github.com/eugef/node-mocks-http/issues/231
108 |
109 | ## v 1.10.0
110 |
111 | - Add support for req.hostname [Issue #224][224].
112 | - Allow to chain writeHead() [Issue #229][229].
113 |
114 | [224]: https://github.com/eugef/node-mocks-http/issues/224
115 | [229]: https://github.com/eugef/node-mocks-http/issues/229
116 |
117 | ## v 1.9.0
118 |
119 | - Implement response.getHeaderNames() and response.hasHeader() [Issue #222][222].
120 | - Remove calls to deprecated Buffer constructors [Issue #221][221].
121 | - Run tests for Node 10, 12 and 14. Drop support for Node 6 and 8 [Issue #218][218].
122 | - Implement response.getHeaders() [Issue #217][217].
123 | - Add req.subdomains [Issue #213][213].
124 | - Add socket option to mockRequest [Issue #209][209].
125 | - Fix index.d.ts [Issue #205][205].
126 | - Added support for response.writableEnded and response.writableFinished [Issue #205][203].
127 |
128 | [222]: https://github.com/eugef/node-mocks-http/issues/222
129 | [221]: https://github.com/eugef/node-mocks-http/issues/221
130 | [218]: https://github.com/eugef/node-mocks-http/issues/218
131 | [217]: https://github.com/eugef/node-mocks-http/issues/217
132 | [213]: https://github.com/eugef/node-mocks-http/issues/213
133 | [209]: https://github.com/eugef/node-mocks-http/issues/209
134 | [205]: https://github.com/eugef/node-mocks-http/issues/205
135 | [203]: https://github.com/eugef/node-mocks-http/issues/203
136 |
137 | ## v 1.8.1
138 |
139 | - Enable res.render() callback argument [Issue #197][197].
140 |
141 | [197]: https://github.com/eugef/node-mocks-http/issues/197
142 |
143 | ## v 1.8.0
144 |
145 | - Added types for IncomingHeaders [Issue #192][192].
146 | - Enabled method chaining [Issue #191][191].
147 | - Added accepts language [Issue #188][188].
148 |
149 | [192]: https://github.com/eugef/node-mocks-http/issues/192
150 | [191]: https://github.com/eugef/node-mocks-http/issues/191
151 | [188]: https://github.com/eugef/node-mocks-http/issues/188
152 |
153 | ## v 1.7.6
154 |
155 | - Fix for [Issue #182][182].
156 |
157 | [182]: https://github.com/eugef/node-mocks-http/issues/182
158 |
159 | ## v 1.7.5
160 |
161 | - Updated the dependency tree with newer versions of `eslint`.
162 |
163 | ## v 1.7.4
164 |
165 | - Added `_getJSONData` function with data sent to the user as JSON. [#181][181]
166 |
167 | [181]: https://github.com/eugef/node-mocks-http/pull/181
168 |
169 | ## v 1.7.3
170 |
171 | - Added `.range()` on a mocked request mimicking the [same function](http://expressjs.com/en/4x/api.html#req.range) on Express' request. [#175][175]
172 |
173 | [175]: https://github.com/eugef/node-mocks-http/pull/175
174 |
175 | ## v 1.7.2
176 |
177 | - Revert Turn mock request into a stream [#174][174]
178 | - Fix security issues reported by npm audit
179 |
180 | [174]: https://github.com/eugef/node-mocks-http/pull/174
181 |
182 | ## v 1.7.1
183 |
184 | - Turn mock request into a stream [#169][169]
185 | - Added missing typings for "locals" & create a helper method to get locals [#170][170]
186 | - Make header names case-insensitive in response [#172][172]
187 | - Throw an error object instead of a string [#173][173]
188 |
189 | [169]: https://github.com/eugef/node-mocks-http/pull/169
190 | [170]: https://github.com/eugef/node-mocks-http/pull/170
191 | [172]: https://github.com/eugef/node-mocks-http/pull/172
192 | [173]: https://github.com/eugef/node-mocks-http/pull/173
193 |
194 | ## v 1.7.0
195 |
196 | - Add support for Buffer payload [#154][154]
197 | - Send request body/payload to trigger relevant events [#164][164]
198 |
199 | [154]: https://github.com/eugef/node-mocks-http/pull/154
200 | [164]: https://github.com/eugef/node-mocks-http/pull/164
201 |
202 | ## v 1.6.8
203 |
204 | - Better typings, including the following (see [PR #158][158] for details):
205 | - request object for a controller fn which is typed as extension of `express.Request`
206 | - same for `Response`
207 | - custom properties appended to request object
208 | - fixed missing `_getRenderView` method on `Response`
209 |
210 | **Note:** As of this release, we are officially supporting:
211 |
212 | - 6.13
213 | - 8.9
214 | - 9.6
215 |
216 | [158]: https://github.com/eugef/node-mocks-http/pull/158
217 |
218 | ## v 1.6.7
219 |
220 | - Set an expiration date to a cookie when deleting it [#155][155]
221 | - No `finish` event, `end` event called when it shouldn't be. [#112][112]
222 | - Add support for [append][] on MockResponse [#143][143]
223 | - Add [locals][] object to response [#135][135]
224 |
225 | Special shoutout to [Eugene Fidelin](https://github.com/eugef) for
226 | joining the team and helping out so much.
227 |
228 | [112]: https://github.com/eugef/node-mocks-http/issues/112
229 | [135]: https://github.com/eugef/node-mocks-http/issues/135
230 | [143]: https://github.com/eugef/node-mocks-http/issues/143
231 | [155]: https://github.com/eugef/node-mocks-http/issues/155
232 | [append]: http://expressjs.com/en/api.html#res.append
233 | [locals]: https://expressjs.com/en/api.html#res.locals
234 |
235 | ## v 1.6.6
236 |
237 | - Upgrade Fresh dependency to 0.5.2 due to a [Security advisory][166-SA]. [PR #147](https://github.com/eugef/node-mocks-http/pull/147)
238 | - Add the baseUrl property to the request object. [PR #150](https://github.com/eugef/node-mocks-http/pull/150)
239 |
240 | [166-SA]: https://nodesecurity.io/advisories/526
241 |
242 | ## v 1.6.5
243 |
244 | - Query type definition now more flexible [PR #146](https://github.com/eugef/node-mocks-http/pull/146)
245 |
246 | ## v 1.6.4
247 |
248 | - Incorporated a trimmed down published NPM artifact PR #141
249 |
250 | ## v 1.6.3
251 |
252 | - Moved @types/express to dev-dependencies. [PR #136][136]
253 |
254 | [136]: https://github.com/eugef/node-mocks-http/issues/136
255 |
256 | ## v 1.6.1
257 |
258 | - Fix for Issue #130 for method chaining for `cookie()` and `clearCookie()`
259 | - Fix for Issue #131 for adding `finished` to the response
260 |
261 | ## v 1.6.0
262 |
263 | - Dropping support for Node's "0" version, but will continue to support v4.
264 | - Verifying our builds with v6 (latest stable) as well as current work (v7)
265 | - Removing dependency on lodash and other bug fixes
266 |
267 | ## v 1.5.4
268 |
269 | - Call `write` method from json method of `responseMock` [PR #98][98]
270 |
271 | [98]: https://github.com/eugef/node-mocks-http/issues/98
272 |
273 | ## v 1.5.3
274 |
275 | - Add `.format` to the `mockResponse` object [PR #94][94]
276 | - Add `.location` to the `mockResponse` object [PR #96][96]
277 | - Add API method, `createMocks` to create both mocks with correct references
278 |
279 | [96]: https://github.com/eugef/node-mocks-http/issues/96
280 | [94]: https://github.com/eugef/node-mocks-http/issues/94
281 |
282 | ## v 1.5.2
283 |
284 | - Add case insensitive response headers [#85][85]
285 | - Fix behavior of `mockResponse.writeHead` [#92][92]
286 | - Add support for statusMessage [#84][84]
287 | - Fix issue with `req.param` not returning when false [#82][82]
288 | - Other bug fixes
289 |
290 | [92]: https://github.com/eugef/node-mocks-http/issues/92
291 | [84]: https://github.com/eugef/node-mocks-http/issues/84
292 | [82]: https://github.com/eugef/node-mocks-http/issues/82
293 | [85]: https://github.com/eugef/node-mocks-http/issues/85
294 |
295 | ## v 1.5.1
296 |
297 | - Add support for the `.vary()` response method
298 |
299 | ## v 1.5.0
300 |
301 | Documentation changes, a new feature, and better behaviors, including:
302 |
303 | - Added `jsonp` method that takes a status code and a payload, see [PR #79][79]
304 | - Now able to attach non-standard properties to the mock request object. [PR #74][74]
305 | - param now takes a default value, see [PR #76][76]
306 | - Emit `end` when redirecting, see [PR #77][77]
307 | - Documentation changes, see [PR #64][64], [PR #75][75], [PR #78][78]
308 |
309 | [64]: https://github.com/eugef/node-mocks-http/issues/64
310 | [74]: https://github.com/eugef/node-mocks-http/issues/74
311 | [75]: https://github.com/eugef/node-mocks-http/issues/75
312 | [76]: https://github.com/eugef/node-mocks-http/issues/76
313 | [77]: https://github.com/eugef/node-mocks-http/issues/77
314 | [78]: https://github.com/eugef/node-mocks-http/issues/78
315 | [79]: https://github.com/eugef/node-mocks-http/issues/79
316 |
317 | ## v 1.4.4
318 |
319 | Bug fix release, including the following:
320 |
321 | - Fixed for [#67][67]
322 | - Merge fix for [#68][68]
323 | - Merge fix for [#70][70]
324 | - Merge fix for [#73][73]
325 |
326 | [67]: https://github.com/eugef/node-mocks-http/issues/67
327 | [68]: https://github.com/eugef/node-mocks-http/issues/68
328 | [70]: https://github.com/eugef/node-mocks-http/issues/70
329 | [73]: https://github.com/eugef/node-mocks-http/issues/73
330 |
331 | ## v 1.2.0
332 |
333 | - Adds a `.header` and `.get` method to the request.
334 |
335 | ## v 1.1.0
336 |
337 | - Adds a `.header`, `.set`, and `.get` method to the response.
338 |
339 | ## v 1.0.4
340 |
341 | - Adds the MIT license
342 |
343 | ## v 1.0.3
344 |
345 | - Merged changes by [invernizzie](https://github.com/invernizzie):
346 | to address [#11](https://github.com/eugef/node-mocks-http/pull/11)
347 |
348 | - Merged changes by [ericchaves](https://github.com/ericchaves):
349 | > I extended your library a little but so it could also handle
350 | > some structured responses. By doing so res.send now evaluate the
351 | > data passed and search for either a statusCode or httpCode to be
352 | > used, and also for a body to send as \_data.
353 | >
354 | > It still working as expected (at least tests passed) for regular
355 | > HTTP responses.
356 | >
357 | > Although I did it with node-restify in mind, it should work well
358 | > for all other libs.
359 |
360 | ## v 1.0.2
361 |
362 | - Adds a `.json()` method to the response. (Thanks, diachedelic)
363 | - Cleaned up all source files so ./run-tests passes.
364 | - Cleaned up jshint issues.
365 |
366 | ## v 1.0.1
367 |
368 | - Adds support for response redirect and render
369 |
370 | ## v 0.0.9
371 |
372 | - Adds support for response cookies
373 |
374 | ## v 0.0.8
375 |
376 | - Adds support for request headers
377 | - Fix wrong function name of set cookies
378 |
379 | ## v 0.0.7
380 |
381 | - Adds support for request cookies
382 |
383 | ## v 0.0.6
384 |
385 | - Adds support for request files
386 |
387 | ## v 0.0.5
388 |
389 | - Fixed a bug where `response.send()` can take two parameters, the status code and the data to send.
390 |
391 | ## v 0.0.4
392 |
393 | - Adds a `request.session` that can be set during construction (or via calling the `_setSessionVariable()` method, and read as an object.
394 |
395 | ## v 0.0.3
396 |
397 | - Adds a `request.query` that can be set during construction and read as an object.
398 |
399 | ## v 0.0.2
400 |
401 | - Code refactoring of the `Response` mock.
402 |
403 | ## v 0.0.1
404 |
405 | - Initial code banged out one late night...
406 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This project is bound by the MIT License (MIT)
2 |
3 | Copyright (c) 2012-2014, Howard Abrams and other collaborators
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/eugef/node-mocks-http)
2 |
3 | ---
4 |
5 | [![NPM version][npm-badge]][npm-url]
6 |
7 | Mock 'http' objects for testing [Express][express-url], [Next.js][nextjs-url] and [Koa][koa-url] routing functions,
8 | but could be used for testing any [Node.js][node-url] web server applications that have code that requires mockups of the `request` and `response` objects.
9 |
10 | ## Installation
11 |
12 | This project is available as a
13 | [NPM package][npm-url].
14 |
15 | ```bash
16 | $ npm install node-mocks-http --save-dev
17 | $ npm install @types/node @types/express --save-dev # when using TypeScript
18 | ```
19 |
20 | or
21 |
22 | ```bash
23 | $ yarn add node-mocks-http --dev
24 | $ yarn add @types/node @types/express --dev # when using TypeScript
25 | ```
26 |
27 | After installing the package include the following in your test files:
28 |
29 | ```js
30 | const httpMocks = require('node-mocks-http');
31 | ```
32 |
33 | ## Usage
34 |
35 | Suppose you have the following Express route:
36 |
37 | ```js
38 | app.get('/user/:id', routeHandler);
39 | ```
40 |
41 | And you have created a function to handle that route's call:
42 |
43 | ```js
44 | const routeHandler = function( request, response ) { ... };
45 | ```
46 |
47 | You can easily test the `routeHandler` function with some code like
48 | this using the testing framework of your choice:
49 |
50 | ```js
51 | exports['routeHandler - Simple testing'] = function (test) {
52 | const request = httpMocks.createRequest({
53 | method: 'GET',
54 | url: '/user/42',
55 | params: {
56 | id: 42
57 | }
58 | });
59 |
60 | const response = httpMocks.createResponse();
61 |
62 | routeHandler(request, response);
63 |
64 | const data = response._getJSONData(); // short-hand for JSON.parse( response._getData() );
65 | test.equal('Bob Dog', data.name);
66 | test.equal(42, data.age);
67 | test.equal('bob@dog.com', data.email);
68 |
69 | test.equal(200, response.statusCode);
70 | test.ok(response._isEndCalled());
71 | test.ok(response._isJSON());
72 | test.ok(response._isUTF8());
73 |
74 | test.done();
75 | };
76 | ```
77 |
78 | ### TypeScript typings
79 |
80 | The typings for TypeScript are bundled with this project. In particular, the `.createRequest()`, `.createResponse()` and `.createMocks()` methods are typed and are generic. Unless specified explicitly, they will be return an Express-based request/response object:
81 |
82 | ```ts
83 | it('should handle expressjs requests', () => {
84 | const mockExpressRequest = httpMocks.createRequest({
85 | method: 'GET',
86 | url: '/user/42',
87 | params: {
88 | id: 42
89 | }
90 | });
91 | const mockExpressResponse = httpMocks.createResponse();
92 |
93 | routeHandler(request, response);
94 |
95 | const data = response._getJSONData();
96 | test.equal('Bob Dog', data.name);
97 | test.equal(42, data.age);
98 | test.equal('bob@dog.com', data.email);
99 |
100 | test.equal(200, response.statusCode);
101 | test.ok(response._isEndCalled());
102 | test.ok(response._isJSON());
103 | test.ok(response._isUTF8());
104 |
105 | test.done();
106 | });
107 | ```
108 |
109 | The expected type parameter in the mock request and response expects any type that extends the NodeJS
110 | `http.IncomingRequest` interface or Fetch API `Request` class. This means you can also mock requests
111 | coming from other frameworks too. An example for NextJS request will look like this:
112 |
113 | ```ts
114 | it('should handle nextjs requests', () => {
115 | const mockExpressRequest = httpMocks.createRequest({
116 | method: 'GET',
117 | url: '/user/42',
118 | params: {
119 | id: 42
120 | }
121 | });
122 | const mockExpressResponse = httpMocks.createResponse();
123 |
124 | // ... the rest of the test as above.
125 | });
126 | ```
127 |
128 | It is also possible to mock requests from the NextJS new AppRouter:
129 |
130 | ```ts
131 | it('should handle nextjs app reouter requests', () => {
132 | const mockExpressRequest = httpMocks.createRequest({
133 | method: 'GET',
134 | url: '/user/42',
135 | params: {
136 | id: 42
137 | }
138 | });
139 | const mockExpressResponse = httpMocks.createResponse();
140 |
141 | // ... the rest of the test as above.
142 | });
143 | ```
144 |
145 | ## API
146 |
147 | ### .createRequest()
148 |
149 | ```
150 | httpMocks.createRequest(options)
151 | ```
152 |
153 | Where options is an object hash with any of the following values:
154 |
155 | | option | description | default value |
156 | | --------------- | -------------------------------- | ------------- |
157 | | `method` | request HTTP method | 'GET' |
158 | | `url` | request URL | '' |
159 | | `originalUrl` | request original URL | `url` |
160 | | `baseUrl` | request base URL | `url` |
161 | | `path` | request path | '' |
162 | | `params` | object hash with params | {} |
163 | | `session` | object hash with session values | `undefined` |
164 | | `cookies` | object hash with request cookies | {} |
165 | | `socket` | object hash with request socket | {} |
166 | | `signedCookies` | object hash with signed cookies | `undefined` |
167 | | `headers` | object hash with request headers | {} |
168 | | `body` | object hash with body | {} |
169 | | `query` | object hash with query values | {} |
170 | | `files` | object hash with values | {} |
171 |
172 | The object returned from this function also supports the [Express request](http://expressjs.com/en/4x/api.html#req) functions ([`.accepts()`](http://expressjs.com/en/4x/api.html#req.accepts), [`.is()`](http://expressjs.com/en/4x/api.html#req.is), [`.get()`](http://expressjs.com/en/4x/api.html#req.get), [`.range()`](http://expressjs.com/en/4x/api.html#req.range), etc.). Please send a PR for any missing functions.
173 |
174 | ### .createResponse()
175 |
176 | ```js
177 | httpMocks.createResponse(options);
178 | ```
179 |
180 | Where options is an object hash with any of the following values:
181 |
182 | | option | description | default value |
183 | | ---------------- | ----------------------------------------------- | -------------------- |
184 | | `locals` | object that contains `response` local variables | `{}` |
185 | | `eventEmitter` | event emitter used by `response` object | `mockEventEmitter` |
186 | | `writableStream` | writable stream used by `response` object | `mockWritableStream` |
187 | | `req` | Request object being responded to | null |
188 |
189 | > NOTE: The out-of-the-box mock event emitter included with `node-mocks-http` is
190 | > not a functional event emitter and as such does not actually emit events. If you
191 | > wish to test your event handlers you will need to bring your own event emitter.
192 |
193 | > Here's an example:
194 |
195 | ```js
196 | const httpMocks = require('node-mocks-http');
197 | const res = httpMocks.createResponse({
198 | eventEmitter: require('events').EventEmitter
199 | });
200 |
201 | // ...
202 | it('should do something', function(done) {
203 | res.on('end', function() {
204 | assert.equal(...);
205 | done();
206 | });
207 | });
208 | // ...
209 | ```
210 |
211 | > This is an example to send request body and trigger it's 'data' and 'end' events:
212 |
213 | ```js
214 | const httpMocks = require('node-mocks-http');
215 | const req = httpMocks.createRequest();
216 | const res = httpMocks.createResponse({
217 | eventEmitter: require('events').EventEmitter
218 | });
219 |
220 | // ...
221 | it('should do something', function (done) {
222 | res.on('end', function () {
223 | expect(response._getData()).to.equal('data sent in request');
224 | done();
225 | });
226 |
227 | route(req, res);
228 |
229 | req.send('data sent in request');
230 | });
231 |
232 | function route(req, res) {
233 | let data = [];
234 | req.on('data', (chunk) => {
235 | data.push(chunk);
236 | });
237 | req.on('end', () => {
238 | data = Buffer.concat(data);
239 | res.write(data);
240 | res.end();
241 | });
242 | }
243 | // ...
244 | ```
245 |
246 | ### .createMocks()
247 |
248 | ```js
249 | httpMocks.createMocks(reqOptions, resOptions);
250 | ```
251 |
252 | Merges `createRequest` and `createResponse`. Passes given options object to each
253 | constructor. Returns an object with properties `req` and `res`.
254 |
255 | ## Design Decisions
256 |
257 | We wanted some simple mocks without a large framework.
258 |
259 | We also wanted the mocks to act like the original framework being
260 | mocked, but allow for setting of values before calling and inspecting
261 | of values after calling.
262 |
263 | ## For Developers
264 |
265 | We are looking for more volunteers to bring value to this project,
266 | including the creation of more objects from the
267 | [HTTP module][node-http-module-url].
268 |
269 | This project doesn't address all features that must be
270 | mocked, but it is a good start. Feel free to send pull requests,
271 | and a member of the team will be timely in merging them.
272 |
273 | If you wish to contribute please read our [Contributing Guidelines](CONTRIBUTING.md).
274 |
275 | ## Release Notes
276 |
277 | Most releases fix bugs with our mocks or add features similar to the
278 | actual `Request` and `Response` objects offered by Node.js and extended
279 | by Express.
280 |
281 | See the [Release History](HISTORY.md) for details.
282 |
283 | [release-notes]: https://github.com/eugef/node-mocks-http/releases
284 |
285 | ## License
286 |
287 | Licensed under [MIT](LICENSE).
288 |
289 | [npm-badge]: https://badge.fury.io/js/node-mocks-http.png
290 | [npm-url]: https://www.npmjs.com/package/node-mocks-http
291 | [express-url]: https://expressjs.com
292 | [nextjs-url]: https://nextjs.org
293 | [koa-url]: https://koajs.com
294 | [node-url]: http://www.nodejs.org
295 | [node-http-module-url]: http://nodejs.org/docs/latest/api/http.html
296 |
--------------------------------------------------------------------------------
/examples/express-route.js:
--------------------------------------------------------------------------------
1 | const httpMocks = require('../lib/http-mock');
2 |
3 | // Suppose you have the following Express route:
4 |
5 | // app.get('/user/:id', routeHandler);
6 |
7 | // And you have created a function to handle that route's call:
8 |
9 | const routeHandler = function (request, response) {
10 | const { id } = request.params;
11 |
12 | console.log("We have a '%s' request for %s (ID: %d)", request.method, request.url, id);
13 |
14 | const body = {
15 | name: 'Bob Dog',
16 | age: 42,
17 | email: 'bob@dog.com'
18 | };
19 |
20 | response.setHeader('Content-Type', 'application/json');
21 | response.statusCode = 200;
22 | response.send(JSON.stringify(body), 'utf8');
23 | response.end();
24 | };
25 |
26 | // -----------------------------------------------------------------
27 | // In another file, you can easily test the routeHandler function
28 | // with some code like this using the testing framework of your choice:
29 |
30 | exports['routeHandler - Simple testing'] = function testing(test) {
31 | const request = httpMocks.createRequest({
32 | method: 'GET',
33 | url: '/user/42',
34 | params: {
35 | id: 42
36 | }
37 | });
38 |
39 | const response = httpMocks.createResponse();
40 |
41 | routeHandler(request, response);
42 |
43 | const data = response._getJSONData();
44 |
45 | test.equal('Bob Dog', data.name);
46 | test.equal(42, data.age);
47 | test.equal('bob@dog.com', data.email);
48 |
49 | test.equal(200, response.statusCode);
50 | test.ok(response._isEndCalled());
51 | test.ok(response._isJSON());
52 | test.ok(response._isUTF8());
53 |
54 | test.done();
55 | };
56 |
--------------------------------------------------------------------------------
/examples/express-status-vs-json.js:
--------------------------------------------------------------------------------
1 | const httpMocks = require('../lib/http-mock');
2 |
3 | // Suppose you have the following Express route:
4 |
5 | // app.post('/users', routeHandler);
6 |
7 | // And you have created a function to handle that route's call:
8 |
9 | const routeHandler = function (request, response) {
10 | console.log("We have a '%s' request for %s", request.method, request.url);
11 |
12 | const body = {
13 | name: 'Bob Dog',
14 | age: 42,
15 | email: 'bob@dog.com'
16 | };
17 |
18 | response.status(201).json(body);
19 | response.end();
20 | };
21 |
22 | // -----------------------------------------------------------------
23 | // In another file, you can easily test the routeHandler function
24 | // with some code like this using the testing framework of your choice:
25 |
26 | exports['routeHandler - Simple testing of status() vs json()'] = function testing(test) {
27 | const request = httpMocks.createRequest({
28 | method: 'POST',
29 | url: '/users'
30 | });
31 |
32 | const response = httpMocks.createResponse();
33 |
34 | routeHandler(request, response);
35 |
36 | const data = response._getJSONData();
37 |
38 | test.equal('Bob Dog', data.name);
39 | test.equal(42, data.age);
40 | test.equal('bob@dog.com', data.email);
41 |
42 | test.equal(201, response.statusCode);
43 | test.ok(response._isJSON());
44 |
45 | test.done();
46 | };
47 |
--------------------------------------------------------------------------------
/lib/express/mock-application.js:
--------------------------------------------------------------------------------
1 | const methods = require('methods');
2 | const deprecate = require('depd')('express');
3 |
4 | const app = {};
5 | const trustProxyDefaultSymbol = '@@symbol:trust_proxy_default';
6 |
7 | app.init = function init() {
8 | this.cache = {};
9 | this.settings = {};
10 | this.engines = {};
11 | this.defaultConfiguration();
12 | };
13 |
14 | app.defaultConfiguration = function defaultConfiguration() {
15 | this.enable('x-powered-by');
16 | this.set('etag', 'weak');
17 | const env = process.env.NODE_ENV || 'development';
18 | this.set('env', env);
19 | this.set('query parser', 'extended');
20 | this.set('subdomain offset', 2);
21 | this.set('trust proxy', false);
22 | Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
23 | configurable: true,
24 | value: true
25 | });
26 |
27 | this.locals = Object.create(null);
28 | this.mountpath = '/';
29 | this.locals.settings = this.settings;
30 | this.set('jsonp callback name', 'callback');
31 |
32 | if (env === 'production') {
33 | this.enable('view cache');
34 | }
35 |
36 | Object.defineProperty(this, 'router', {
37 | get() {
38 | throw new Error(
39 | "'app.router' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app."
40 | );
41 | }
42 | });
43 | };
44 |
45 | app.lazyrouter = () => {};
46 | app.handle = () => {};
47 | app.route = () => {};
48 | app.render = () => {};
49 | app.listen = () => {};
50 |
51 | app.use = function use() {
52 | return this;
53 | };
54 |
55 | app.engine = function engine() {
56 | return this;
57 | };
58 |
59 | app.param = function param() {
60 | return this;
61 | };
62 |
63 | app.set = function set(setting, val) {
64 | if (arguments.length === 1) {
65 | return this.settings[setting];
66 | }
67 |
68 | this.settings[setting] = val;
69 | return this;
70 | };
71 |
72 | app.path = function path() {
73 | return '';
74 | };
75 |
76 | app.enabled = function enabled(setting) {
77 | return !!this.set(setting);
78 | };
79 |
80 | app.disabled = function disabled(setting) {
81 | return !this.set(setting);
82 | };
83 |
84 | app.enable = function enable(setting) {
85 | return this.set(setting, true);
86 | };
87 |
88 | app.disable = function disable(setting) {
89 | return this.set(setting, false);
90 | };
91 |
92 | methods.forEach((method) => {
93 | app[method] = function httpMethod() {
94 | return this;
95 | };
96 | });
97 |
98 | app.all = function all() {
99 | return this;
100 | };
101 |
102 | app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead');
103 |
104 | module.exports = app;
105 |
--------------------------------------------------------------------------------
/lib/express/mock-express.js:
--------------------------------------------------------------------------------
1 | const { EventEmitter } = require('events');
2 |
3 | const mixin = require('merge-descriptors');
4 |
5 | const application = require('./mock-application');
6 | const request = require('./mock-request');
7 | const response = require('../mockResponse');
8 |
9 | const expressResponse = {
10 | createResponse: response.createResponse
11 | };
12 |
13 | function createApplication() {
14 | const app = function () {};
15 |
16 | mixin(app, EventEmitter.prototype, false);
17 | mixin(app, application, false);
18 |
19 | app.request = {
20 | __proto__: request,
21 | app
22 | };
23 | app.response = {
24 | __proto__: expressResponse.createResponse(),
25 | app
26 | };
27 | app.init();
28 | return app;
29 | }
30 |
31 | module.exports = createApplication;
32 |
33 | module.exports.application = application;
34 | module.exports.request = request;
35 | module.exports.response = expressResponse;
36 |
--------------------------------------------------------------------------------
/lib/express/mock-request.js:
--------------------------------------------------------------------------------
1 | const accepts = require('accepts');
2 | const typeis = require('type-is');
3 | const parseRange = require('range-parser');
4 | const parse = require('parseurl');
5 | const { isIP } = require('net');
6 | const fresh = require('fresh');
7 |
8 | const http = require('http');
9 | const defineGetter = require('./utils/define-getter');
10 |
11 | const req = {
12 | __proto__: http.IncomingMessage.prototype
13 | };
14 |
15 | req.header = function header(name) {
16 | const headerName = name.toLowerCase();
17 | switch (headerName) {
18 | case 'referer':
19 | case 'referrer':
20 | return this.headers.referrer || this.headers.referer;
21 | default:
22 | return this.headers[headerName];
23 | }
24 | };
25 |
26 | req.get = req.header;
27 |
28 | req.accepts = function acceptTypes(...args) {
29 | const accept = accepts(this);
30 | return accept.types(...args);
31 | };
32 |
33 | req.acceptsEncodings = function acceptsEncodings(...args) {
34 | const accept = accepts(this);
35 | return accept.encodings(...args);
36 | };
37 |
38 | req.acceptsEncoding = req.acceptsEncodings;
39 |
40 | req.acceptsCharsets = function acceptsCharsets(...args) {
41 | const accept = accepts(this);
42 | return accept.charsets(...args);
43 | };
44 |
45 | req.acceptsCharset = req.acceptsCharsets;
46 |
47 | req.acceptsLanguages = function acceptsLanguages(...args) {
48 | const accept = accepts(this);
49 | return accept.languages(...args);
50 | };
51 |
52 | req.acceptsLanguage = req.acceptsLanguages;
53 |
54 | req.range = function getRange(size) {
55 | const range = this.get('Range');
56 | if (!range) {
57 | return undefined;
58 | }
59 | return parseRange(size, range);
60 | };
61 |
62 | req.param = function param(name, defaultValue) {
63 | const params = this.params || {};
64 | const body = this.body || {};
65 | const query = this.query || {};
66 |
67 | if (params[name] !== null && {}.hasOwnProperty.call(params, name)) {
68 | return params[name];
69 | }
70 | if (body[name] !== null) {
71 | return body[name];
72 | }
73 | if (query[name] !== null) {
74 | return query[name];
75 | }
76 |
77 | return defaultValue;
78 | };
79 |
80 | req.is = function is(...args) {
81 | let types = args;
82 |
83 | if (Array.isArray(args[0])) {
84 | types = args[0];
85 | }
86 |
87 | return typeis(this, types);
88 | };
89 |
90 | defineGetter(req, 'protocol', function protocol() {
91 | let { proto } = this.options;
92 | proto = this.get('X-Forwarded-Proto') || proto;
93 | return proto.split(/\s*,\s*/)[0];
94 | });
95 |
96 | defineGetter(req, 'secure', function secure() {
97 | return this.protocol === 'https';
98 | });
99 |
100 | defineGetter(req, 'ip', function ip() {
101 | return this.options.ip || '127.0.0.1';
102 | });
103 |
104 | defineGetter(req, 'ips', function ips() {
105 | return [this.ip];
106 | });
107 |
108 | defineGetter(req, 'subdomains', function subdomains() {
109 | const { hostname } = this;
110 |
111 | if (!hostname) {
112 | return [];
113 | }
114 |
115 | const offset = this.app.get('subdomain offset');
116 | const domains = !isIP(hostname) ? hostname.split('.').reverse() : [hostname];
117 |
118 | return domains.slice(offset);
119 | });
120 |
121 | defineGetter(req, 'path', function path() {
122 | return parse(this).pathname;
123 | });
124 |
125 | defineGetter(req, 'hostname', function hostname() {
126 | let host = this.get('X-Forwarded-Host');
127 |
128 | if (!host) {
129 | host = this.get('Host');
130 | }
131 |
132 | if (!host) {
133 | return undefined;
134 | }
135 |
136 | const offset = host[0] === '[' ? host.indexOf(']') + 1 : 0;
137 | const index = host.indexOf(':', offset);
138 |
139 | return index < 0 ? host.substring(0, index) : host;
140 | });
141 |
142 | defineGetter(req, 'host', function host() {
143 | return this.hostname;
144 | });
145 |
146 | defineGetter(req, 'fresh', function isFresh() {
147 | const { method } = this;
148 | const { statusCode } = this.res;
149 |
150 | if (method !== 'GET' && method !== 'HEAD') {
151 | return false;
152 | }
153 |
154 | if ((statusCode >= 200 && statusCode < 300) || statusCode === 304) {
155 | return fresh(this.headers, this.res._headers || {});
156 | }
157 |
158 | return false;
159 | });
160 |
161 | defineGetter(req, 'stale', function stale() {
162 | return !this.fresh;
163 | });
164 |
165 | defineGetter(req, 'xhr', function xhr() {
166 | const val = this.get('X-Requested-With') || '';
167 | return val.toLowerCase() === 'xmlhttprequest';
168 | });
169 |
170 | module.exports = req;
171 |
--------------------------------------------------------------------------------
/lib/express/utils/define-getter.js:
--------------------------------------------------------------------------------
1 | function defineGetter(obj, name, getter) {
2 | Object.defineProperty(obj, name, {
3 | configurable: true,
4 | enumerable: true,
5 | get: getter
6 | });
7 | }
8 |
9 | module.exports = defineGetter;
10 |
--------------------------------------------------------------------------------
/lib/headers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file implements the Web API Headers interface
3 | * (https://developer.mozilla.org/en-US/docs/Web/API/Headers)
4 | * while maintaining compatibility with Express.js style header access.
5 | */
6 | const utils = require('./utils');
7 |
8 | const REFERER_HEADER_NAMES = ['referer', 'referrer'];
9 |
10 | /**
11 | * Get a header value from the headers object.
12 | * Because headers can be set in multiple ways, their names can be uppercased and lowercased.
13 | *
14 | * @param {Object} headers
15 | * @param {string} name
16 | * @returns
17 | */
18 | function getHeaderValue(headers, name) {
19 | const lowerName = name.toLowerCase();
20 |
21 | return headers[
22 | Object.keys(headers).find((key) => {
23 | const lowerKey = key.toLowerCase();
24 | return (
25 | lowerKey === lowerName ||
26 | (REFERER_HEADER_NAMES.includes(lowerKey) && REFERER_HEADER_NAMES.includes(lowerName))
27 | );
28 | })
29 | ];
30 | }
31 |
32 | /**
33 | * Creates a Headers object that implements both Express.js style access
34 | * and the Web API Headers interface
35 | *
36 | * @param {Object} headers - Initial headers object
37 | * @returns {HeaderWebAPI} - A proxy that implements the HeaderWebAPI interface
38 | */
39 | function createHeaders(headers = {}) {
40 | return new Proxy(utils.convertKeysToLowerCase(headers), {
41 | get(target, prop) {
42 | // Handle Headers interface methods
43 | switch (prop) {
44 | case 'get':
45 | return (name) => getHeaderValue(target, name);
46 | case 'getAll':
47 | return (name) => {
48 | const value = getHeaderValue(target, name);
49 | if (!value) {
50 | return [];
51 | }
52 | return Array.isArray(value) ? value : [value];
53 | };
54 | case 'has':
55 | return (name) => getHeaderValue(target, name) !== undefined;
56 | case 'set':
57 | return (name, value) => {
58 | // eslint-disable-next-line no-param-reassign
59 | target[name.toLowerCase()] = value;
60 | };
61 | case 'append':
62 | return (name, value) => {
63 | const lowerName = name.toLowerCase();
64 | if (lowerName in target) {
65 | const existingValue = target[lowerName];
66 | if (Array.isArray(existingValue)) {
67 | existingValue.push(value);
68 | } else {
69 | // eslint-disable-next-line no-param-reassign
70 | target[lowerName] = [existingValue, value];
71 | }
72 | } else {
73 | // eslint-disable-next-line no-param-reassign
74 | target[lowerName] = value;
75 | }
76 | };
77 | case 'delete':
78 | return (name) => {
79 | // eslint-disable-next-line no-param-reassign
80 | delete target[name.toLowerCase()];
81 | };
82 | case 'forEach':
83 | return (callback, thisArg) => {
84 | Object.entries(target).forEach(([key, value]) => {
85 | callback.call(thisArg, value, key, target);
86 | });
87 | };
88 | case 'entries':
89 | return () => Object.entries(target)[Symbol.iterator]();
90 | case 'keys':
91 | return () => Object.keys(target)[Symbol.iterator]();
92 | case 'values':
93 | return () => Object.values(target)[Symbol.iterator]();
94 | case Symbol.iterator:
95 | return (
96 | target[Symbol.iterator] ||
97 | function* iterator() {
98 | yield* Object.entries(target);
99 | }
100 | );
101 | default:
102 | return typeof prop === 'string' ? getHeaderValue(target, prop) : target[prop];
103 | }
104 | },
105 | set(target, prop, value) {
106 | if (typeof prop === 'string') {
107 | // eslint-disable-next-line no-param-reassign
108 | target[prop.toLowerCase()] = value;
109 | return true;
110 | }
111 | return false;
112 | },
113 | has(target, prop) {
114 | if (typeof prop === 'string') {
115 | return prop.toLowerCase() in target;
116 | }
117 | return prop in target;
118 | },
119 | deleteProperty(target, prop) {
120 | if (typeof prop === 'string') {
121 | // eslint-disable-next-line no-param-reassign
122 | delete target[prop.toLowerCase()];
123 | return true;
124 | }
125 | // eslint-disable-next-line no-param-reassign
126 | return delete target[prop];
127 | },
128 | ownKeys(target) {
129 | return Object.keys(target);
130 | },
131 | getOwnPropertyDescriptor(target, prop) {
132 | return Object.getOwnPropertyDescriptor(target, prop);
133 | }
134 | });
135 | }
136 |
137 | module.exports = { createHeaders, getHeaderValue };
138 |
--------------------------------------------------------------------------------
/lib/http-mock.d.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, CookieOptions } from 'express';
2 | import { IncomingMessage, OutgoingMessage } from 'http';
3 |
4 | export type RequestType = IncomingMessage | globalThis.Request;
5 | export type ResponseType = OutgoingMessage | globalThis.Response;
6 |
7 | export type RequestMethod = 'CONNECT' | 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'TRACE';
8 |
9 | export interface Params {
10 | [key: string]: any;
11 | }
12 |
13 | export interface Session {
14 | [key: string]: any;
15 | }
16 |
17 | export interface Cookies {
18 | [key: string]: string;
19 | }
20 |
21 | export interface Headers {
22 | // Standard HTTP headers
23 | accept?: string;
24 | 'accept-language'?: string;
25 | 'accept-patch'?: string;
26 | 'accept-ranges'?: string;
27 | 'access-control-allow-credentials'?: string;
28 | 'access-control-allow-headers'?: string;
29 | 'access-control-allow-methods'?: string;
30 | 'access-control-allow-origin'?: string;
31 | 'access-control-expose-headers'?: string;
32 | 'access-control-max-age'?: string;
33 | age?: string;
34 | allow?: string;
35 | 'alt-svc'?: string;
36 | authorization?: string;
37 | 'cache-control'?: string;
38 | connection?: string;
39 | 'content-disposition'?: string;
40 | 'content-encoding'?: string;
41 | 'content-language'?: string;
42 | 'content-length'?: string;
43 | 'content-location'?: string;
44 | 'content-range'?: string;
45 | 'content-type'?: string;
46 | cookie?: string;
47 | date?: string;
48 | expect?: string;
49 | expires?: string;
50 | forwarded?: string;
51 | from?: string;
52 | host?: string;
53 | 'if-match'?: string;
54 | 'if-modified-since'?: string;
55 | 'if-none-match'?: string;
56 | 'if-unmodified-since'?: string;
57 | 'last-modified'?: string;
58 | location?: string;
59 | pragma?: string;
60 | 'proxy-authenticate'?: string;
61 | 'proxy-authorization'?: string;
62 | 'public-key-pins'?: string;
63 | range?: string;
64 | referer?: string;
65 | 'retry-after'?: string;
66 | 'set-cookie'?: string[];
67 | 'strict-transport-security'?: string;
68 | tk?: string;
69 | trailer?: string;
70 | 'transfer-encoding'?: string;
71 | upgrade?: string;
72 | 'user-agent'?: string;
73 | vary?: string;
74 | via?: string;
75 | warning?: string;
76 | 'www-authenticate'?: string;
77 |
78 | // Support for arbitrary headers
79 | [header: string]: string | string[] | undefined;
80 | }
81 |
82 | /**
83 | * HeaderWebAPI interface combines the existing Headers type with
84 | * standard Web API Headers interface methods for better compatibility
85 | * with browser environments.
86 | */
87 | export interface HeaderWebAPI {
88 | // Include all the header properties
89 | [header: string]: any; // 'any' to accommodate both header values and methods
90 |
91 | // Web API Headers methods
92 | append(name: string, value: string): void;
93 | delete(name: string): void;
94 | get(name: string): string | null;
95 | has(name: string): boolean;
96 | set(name: string, value: string): void;
97 | forEach(callbackfn: (value: string, key: string, parent: HeaderWebAPI) => void, thisArg?: any): void;
98 |
99 | // Iterator methods
100 | entries(): IterableIterator<[string, string]>;
101 | keys(): IterableIterator;
102 | values(): IterableIterator;
103 | [Symbol.iterator](): IterableIterator<[string, string]>;
104 | }
105 |
106 | export interface Query {
107 | [key: string]: any;
108 | }
109 |
110 | export interface Files {
111 | [key: string]: string;
112 | }
113 |
114 | export interface Body {
115 | [key: string]: any;
116 | }
117 |
118 | export interface RequestOptions {
119 | method?: RequestMethod;
120 | url?: string;
121 | originalUrl?: string;
122 | baseUrl?: string;
123 | path?: string;
124 | params?: Params;
125 | session?: Session;
126 | cookies?: Cookies;
127 | signedCookies?: Cookies;
128 | headers?: Headers;
129 | body?: Body;
130 | query?: Query;
131 | files?: Files;
132 | ip?: string;
133 |
134 | // Support custom properties appended on Request objects.
135 | [key: string]: any;
136 | }
137 |
138 | export type MockRequest = T & {
139 | _setParameter: (key: string, value?: string) => void;
140 | _setSessionVariable: (variable: string, value?: string) => void;
141 | _setCookiesVariable: (variable: string, value?: string) => void;
142 | _setSignedCookiesVariable: (variable: string, value?: string) => void;
143 | _setHeadersCookiesVariable: (variable: string, value: string) => void;
144 | _setFilesCookiesVariable: (variable: string, value?: string) => void;
145 | _setMethod: (method?: string) => void;
146 | _setURL: (value?: string) => void;
147 | _setOriginalUrl: (value?: string) => void;
148 | _setBody: (body?: Body) => void;
149 | _addBody: (key: string, value?: any) => void;
150 |
151 | headers: HeaderWebAPI;
152 |
153 | // Support custom properties appended on Request objects.
154 | [key: string]: any;
155 | };
156 |
157 | export interface ResponseOptions {
158 | eventEmitter?: any;
159 | writableStream?: any;
160 | req?: any;
161 | locals?: any;
162 | }
163 |
164 | export type ResponseCookie = {
165 | value: any;
166 | options: CookieOptions;
167 | };
168 |
169 | export type MockResponse = T & {
170 | _isEndCalled: () => boolean;
171 | _getHeaders: () => HeaderWebAPI;
172 | _getData: () => any;
173 | _getJSONData: () => any;
174 | _getBuffer: () => Buffer;
175 | _getLocals: () => any;
176 | _getStatusCode: () => number;
177 | _getStatusMessage: () => string;
178 | _isJSON: () => boolean;
179 | _isUTF8: () => boolean;
180 | _isDataLengthValid: () => boolean;
181 | _getRedirectUrl: () => string;
182 | _getRenderData: () => any;
183 | _getRenderView: () => string;
184 |
185 | cookies: { [name: string]: ResponseCookie };
186 | };
187 |
188 | export function createRequest(options?: RequestOptions): MockRequest;
189 |
190 | export function createResponse(options?: ResponseOptions): MockResponse;
191 |
192 | export interface Mocks {
193 | req: MockRequest;
194 | res: MockResponse;
195 | }
196 |
197 | export function createMocks(
198 | reqOptions?: RequestOptions,
199 | resOptions?: ResponseOptions
200 | ): Mocks;
201 |
--------------------------------------------------------------------------------
/lib/http-mock.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module: http-mock
3 | *
4 | * The interface for this entire module that just exposes the exported
5 | * functions from the other libraries.
6 | */
7 |
8 | const request = require('./mockRequest');
9 | const response = require('./mockResponse');
10 | const express = require('./express/mock-express');
11 |
12 | /**
13 | * Creates linked req and res objects. Enables using methods that require both
14 | * objects to interact, for example res.format.
15 | *
16 | * @param {Object} reqOpts Options for req creation, see
17 | * @mockRequest.createRequest
18 | * @param {Object} resOpts Options for res creation, see
19 | * @mockResponse.createResponse
20 | * @return {Object} Object with both mocks: { req, res }
21 | */
22 | const createRequestResponse = function (reqOpts, resOpts) {
23 | const req = request.createRequest(reqOpts);
24 | const res = response.createResponse({ ...resOpts, req });
25 |
26 | return { req, res };
27 | };
28 |
29 | exports.createRequest = request.createRequest;
30 | exports.createResponse = response.createResponse;
31 | exports.createMocks = createRequestResponse;
32 | exports.express = express;
33 |
--------------------------------------------------------------------------------
/lib/mockEventEmitter.js:
--------------------------------------------------------------------------------
1 | /*
2 | * http://nodejs.org/api/events.html
3 | */
4 |
5 | function EventEmitter() {}
6 |
7 | EventEmitter.prototype.addListener = () => {};
8 | EventEmitter.prototype.on = () => {};
9 | EventEmitter.prototype.once = () => {};
10 | EventEmitter.prototype.removeListener = () => {};
11 | EventEmitter.prototype.removeAllListeners = () => {};
12 | // EventEmitter.prototype.removeAllListeners = function([event])
13 | EventEmitter.prototype.setMaxListeners = () => {};
14 | EventEmitter.prototype.listeners = () => {};
15 | EventEmitter.prototype.emit = () => {};
16 | EventEmitter.prototype.prependListener = () => {};
17 | // EventEmitter.prototype.emit = function(event, [arg1], [arg2], [...]){}
18 |
19 | module.exports = EventEmitter;
20 |
--------------------------------------------------------------------------------
/lib/mockRequest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * File: mockRequest
3 | *
4 | * This file implements node.js's implementation of a 'request' object.
5 | * This is actually closer to what Express offers the user, in that the
6 | * body is really a parsed object of values.
7 | *
8 | * @author Howard Abrams
9 | */
10 |
11 | /**
12 | * Function: createRequest
13 | *
14 | * Creates a new mock 'request' instance. All values are reset to the
15 | * defaults.
16 | *
17 | * Parameters:
18 | *
19 | * options - An object of named parameters.
20 | *
21 | * Options:
22 | *
23 | * method - The method value, see
24 | * url - The url value, see
25 | * originalUrl - The originalUrl value, see
26 | * baseUrl - The baseUrl value, see
27 | * params - The parameters, see
28 | * body - The body values, , see
29 | */
30 |
31 | const url = require('url');
32 | const typeis = require('type-is');
33 | const accepts = require('accepts');
34 | const parseRange = require('range-parser');
35 | let { EventEmitter } = require('events');
36 | const querystring = require('querystring');
37 | const { createHeaders, getHeaderValue } = require('./headers');
38 |
39 | const standardRequestOptions = [
40 | 'method',
41 | 'url',
42 | 'originalUrl',
43 | 'baseUrl',
44 | 'path',
45 | 'params',
46 | 'session',
47 | 'cookies',
48 | 'headers',
49 | 'body',
50 | 'query',
51 | 'files'
52 | ];
53 |
54 | function createRequest(options = {}) {
55 | if (options.eventEmitter) {
56 | EventEmitter = options.eventEmitter;
57 | }
58 |
59 | // create mockRequest
60 | const mockRequest = Object.create(EventEmitter.prototype);
61 | EventEmitter.call(mockRequest);
62 |
63 | mockRequest.method = options.method ? options.method : 'GET';
64 | mockRequest.url = options.url || options.path || '';
65 | mockRequest.originalUrl = options.originalUrl || mockRequest.url;
66 | mockRequest.baseUrl = options.baseUrl || mockRequest.url;
67 | mockRequest.path = options.path || (options.url ? url.parse(options.url).pathname : '');
68 | mockRequest.params = options.params ? options.params : {};
69 | if (options.session) {
70 | mockRequest.session = options.session;
71 | }
72 | mockRequest.cookies = options.cookies ? options.cookies : {};
73 | if (options.signedCookies) {
74 | mockRequest.signedCookies = options.signedCookies;
75 | }
76 |
77 | // Create headers using the Headers.js module
78 | mockRequest.headers = createHeaders(options.headers);
79 |
80 | mockRequest.body = options.body ? options.body : {};
81 | mockRequest.query = options.query ? options.query : {};
82 | mockRequest.files = options.files ? options.files : {};
83 | mockRequest.socket = options.socket ? options.socket : {};
84 |
85 | mockRequest.ip = options.ip || '127.0.0.1';
86 | mockRequest.ips = [mockRequest.ip];
87 |
88 | mockRequest.destroy = () => {};
89 |
90 | // parse query string from url to object
91 | if (Object.keys(mockRequest.query).length === 0) {
92 | mockRequest.query = querystring.parse(mockRequest.url.split('?')[1]);
93 |
94 | if (!mockRequest.query.hasOwnProperty) {
95 | Object.defineProperty(mockRequest.query, 'hasOwnProperty', {
96 | enumerable: false,
97 | value: Object.hasOwnProperty.bind(mockRequest.query)
98 | });
99 | }
100 | }
101 |
102 | // attach any other provided objects into the request for more advanced testing
103 | for (const n in options) {
104 | if (standardRequestOptions.indexOf(n) === -1) {
105 | mockRequest[n] = options[n];
106 | }
107 | }
108 |
109 | /**
110 | * Return request header.
111 | *
112 | * The `Referrer` header field is special-cased,
113 | * both `Referrer` and `Referer` are interchangeable.
114 | *
115 | * Examples:
116 | *
117 | * mockRequest.get('Content-Type');
118 | * // => "text/plain"
119 | *
120 | * mockRequest.get('content-type');
121 | * // => "text/plain"
122 | *
123 | * mockRequest.get('Something');
124 | * // => undefined
125 | *
126 | * Aliased as `mockRequest.header()`.
127 | *
128 | * @param {String} name
129 | * @return {String}
130 | * @api public
131 | */
132 | mockRequest.getHeader = function getHeader(name) {
133 | return getHeaderValue(mockRequest.headers, name);
134 | };
135 | mockRequest.header = mockRequest.getHeader;
136 | mockRequest.get = mockRequest.getHeader;
137 |
138 | /**
139 | * Function: is
140 | *
141 | * Checks for matching content types in the content-type header.
142 | * Requires a request body, identified by transfer-encoding or content-length headers
143 | *
144 | * Examples:
145 | *
146 | * mockRequest.headers['content-type'] = 'text/html';
147 | * mockRequest.headers['transfer-encoding'] = 'chunked';
148 | * mockRequest.headers['content-length'] = '100';
149 | *
150 | * mockRequest.is('html');
151 | * // => "html"
152 | *
153 | * mockRequest.is('json');
154 | * // => false
155 | *
156 | * mockRequest.is(['json', 'html', 'text']);
157 | * // => "html"
158 | *
159 | * @param {String|String[]} types content type or array of types to match
160 | * @return {String|false|null} Matching content type as string, false if no match, null if request has no body.
161 | * @api public
162 | */
163 | mockRequest.is = function isContentType(...args) {
164 | let types = args;
165 |
166 | if (Array.isArray(args[0])) {
167 | types = args[0];
168 | }
169 |
170 | return typeis(mockRequest, types);
171 | };
172 |
173 | /**
174 | * Function: accepts
175 | *
176 | * Checks for matching content types in the Accept header.
177 | *
178 | * Examples:
179 | *
180 | * mockRequest.headers['accept'] = 'application/json'
181 | *
182 | * mockRequest.accepts('json');
183 | * // => 'json'
184 | *
185 | * mockRequest.accepts('html');
186 | * // => false
187 | *
188 | * mockRequest.accepts(['html', 'json']);
189 | * // => 'json'
190 | *
191 | * @param {String|String[]} types Mime type(s) to check against
192 | * @return {String|false} Matching type or false if no match.
193 | */
194 | mockRequest.accepts = function acceptsTypes(types) {
195 | const Accepts = accepts(mockRequest);
196 | return Accepts.type(types);
197 | };
198 |
199 | /**
200 | * Check if the given `encoding`s are accepted.
201 | *
202 | * @param {String} ...encoding
203 | * @return {String|Array}
204 | * @public
205 | */
206 | mockRequest.acceptsEncodings = function acceptsEncodings(...args) {
207 | let encodings = args;
208 |
209 | if (Array.isArray(args[0])) {
210 | encodings = args[0];
211 | }
212 |
213 | const accept = accepts(mockRequest);
214 | return accept.encodings(encodings);
215 | };
216 |
217 | /**
218 | * Check if the given `charset`s are acceptable,
219 | * otherwise you should respond with 406 "Not Acceptable".
220 | *
221 | * @param {String} ...charset
222 | * @return {String|Array}
223 | * @public
224 | */
225 | mockRequest.acceptsCharsets = function acceptsCharsets(...args) {
226 | let charsets = args;
227 |
228 | if (Array.isArray(args[0])) {
229 | charsets = args[0];
230 | }
231 |
232 | const accept = accepts(mockRequest);
233 | return accept.charsets(charsets);
234 | };
235 |
236 | /**
237 | * Check if the given `lang`s are acceptable,
238 | * otherwise you should respond with 406 "Not Acceptable".
239 | *
240 | * @param {String} ...lang
241 | * @return {String|Array}
242 | * @public
243 | */
244 | mockRequest.acceptsLanguages = function acceptsLanguages(...args) {
245 | let languages = args;
246 |
247 | if (Array.isArray(args[0])) {
248 | languages = args[0];
249 | }
250 |
251 | const accept = accepts(mockRequest);
252 | return accept.languages(languages);
253 | };
254 |
255 | /**
256 | * Function: range
257 | *
258 | * Parse Range header field, capping to the given `size`.
259 | *
260 | * Unspecified ranges such as "0-" require knowledge of your resource length. In
261 | * the case of a byte range this is of course the total number of bytes. If the
262 | * Range header field is not given `undefined` is returned, `-1` when unsatisfiable,
263 | * and `-2` when syntactically invalid.
264 | *
265 | * When ranges are returned, the array has a "type" property which is the type of
266 | * range that is required (most commonly, "bytes"). Each array element is an object
267 | * with a "start" and "end" property for the portion of the range.
268 | *
269 | * The "combine" option can be set to `true` and overlapping & adjacent ranges
270 | * will be combined into a single range.
271 | *
272 | * NOTE: remember that ranges are inclusive, so for example "Range: users=0-3"
273 | * should respond with 4 users when available, not 3.
274 | *
275 | * @param {number} size
276 | * @param {object} [opts]
277 | * @param {boolean} [opts.combine=false]
278 | * @return {false|number|array}
279 | * @public
280 | */
281 | mockRequest.range = function isRange(size, opts) {
282 | const range = mockRequest.get('Range');
283 | if (!range) {
284 | return undefined;
285 | }
286 | return parseRange(size, range, opts);
287 | };
288 |
289 | /**
290 | * Function: param
291 | *
292 | * Return the value of param name when present.
293 | * Lookup is performed in the following order:
294 | * - req.params
295 | * - req.body
296 | * - req.query
297 | */
298 | mockRequest.param = function param(parameterName, defaultValue) {
299 | if ({}.hasOwnProperty.call(mockRequest.params, parameterName)) {
300 | return mockRequest.params[parameterName];
301 | }
302 | if ({}.hasOwnProperty.call(mockRequest.body, parameterName)) {
303 | return mockRequest.body[parameterName];
304 | }
305 | if ({}.hasOwnProperty.call(mockRequest.query, parameterName)) {
306 | return mockRequest.query[parameterName];
307 | }
308 | return defaultValue;
309 | };
310 |
311 | /**
312 | * Function: _setParameter
313 | *
314 | * Set parameters that the client can then get using the 'params'
315 | * key.
316 | *
317 | * Parameters:
318 | *
319 | * key - The key. For instance, 'bob' would be accessed: request.params.bob
320 | * value - The value to return when accessed.
321 | */
322 | mockRequest._setParameter = function _setParameter(key, value) {
323 | mockRequest.params[key] = value;
324 | };
325 |
326 | /**
327 | * Sets a variable that is stored in the session.
328 | *
329 | * @param variable The variable to store in the session
330 | * @param value The value associated with the variable
331 | */
332 | mockRequest._setSessionVariable = function _setSessionVariable(variable, value) {
333 | if (typeof mockRequest.session !== 'object') {
334 | mockRequest.session = {};
335 | }
336 | mockRequest.session[variable] = value;
337 | };
338 |
339 | /**
340 | * Sets a variable that is stored in the cookies.
341 | *
342 | * @param variable The variable to store in the cookies
343 | * @param value The value associated with the variable
344 | */
345 | mockRequest._setCookiesVariable = function _setCookiesVariable(variable, value) {
346 | mockRequest.cookies[variable] = value;
347 | };
348 |
349 | /**
350 | * Sets a variable that is stored in the signed cookies.
351 | *
352 | * @param variable The variable to store in the signed cookies
353 | * @param value The value associated with the variable
354 | */
355 | mockRequest._setSignedCookiesVariable = function _setSignedCookiesVariable(variable, value) {
356 | if (typeof mockRequest.signedCookies !== 'object') {
357 | mockRequest.signedCookies = {};
358 | }
359 | mockRequest.signedCookies[variable] = value;
360 | };
361 |
362 | /**
363 | * Sets a variable that is stored in the headers.
364 | *
365 | * @param variable The variable to store in the headers
366 | * @param value The value associated with the variable
367 | */
368 | mockRequest._setHeadersVariable = function _setHeadersVariable(variable, value) {
369 | mockRequest.headers[variable] = value;
370 | };
371 |
372 | /**
373 | * Sets a variable that is stored in the files.
374 | *
375 | * @param variable The variable to store in the files
376 | * @param value The value associated with the variable
377 | */
378 | mockRequest._setFilesVariable = function _setFilesVariable(variable, value) {
379 | mockRequest.files[variable] = value;
380 | };
381 |
382 | /**
383 | * Function: _setMethod
384 | *
385 | * Sets the HTTP method that the client gets when the called the 'method'
386 | * property. This defaults to 'GET' if it is not set.
387 | *
388 | * Parameters:
389 | *
390 | * method - The HTTP method, e.g. GET, POST, PUT, DELETE, etc.
391 | *
392 | * Note: We don't validate the string. We just return it.
393 | */
394 | mockRequest._setMethod = function _setMethod(method) {
395 | mockRequest.method = method;
396 | };
397 |
398 | /**
399 | * Function: _setURL
400 | *
401 | * Sets the URL value that the client gets when the called the 'url'
402 | * property.
403 | *
404 | * Parameters:
405 | *
406 | * value - The request path, e.g. /my-route/452
407 | *
408 | * Note: We don't validate the string. We just return it. Typically, these
409 | * do not include hostname, port or that part of the URL.
410 | */
411 | mockRequest._setURL = function _setURL(value) {
412 | mockRequest.url = value;
413 | };
414 |
415 | /**
416 | * Function: _setBaseUrl
417 | *
418 | * Sets the URL value that the client gets when the called the 'baseUrl'
419 | * property.
420 | *
421 | * Parameters:
422 | *
423 | * value - The request base path, e.g. /my-route
424 | *
425 | * Note: We don't validate the string. We just return it. Typically, these
426 | * do not include hostname, port or that part of the URL.
427 | */
428 | mockRequest._setBaseUrl = function _setBaseUrl(value) {
429 | mockRequest.baseUrl = value;
430 | };
431 |
432 | /**
433 | * Function: _setOriginalUrl
434 | *
435 | * Sets the URL value that the client gets when the called the 'originalUrl'
436 | * property.
437 | *
438 | * Parameters:
439 | *
440 | * value - The request path, e.g. /my-route/452
441 | *
442 | * Note: We don't validate the string. We just return it. Typically, these
443 | * do not include hostname, port or that part of the URL.
444 | */
445 | mockRequest._setOriginalUrl = function _setOriginalUrl(value) {
446 | mockRequest.originalUrl = value;
447 | };
448 |
449 | /**
450 | * Function: _setBody
451 | *
452 | * Sets the body that the client gets when the called the 'body'
453 | * parameter. This defaults to 'GET' if it is not set.
454 | *
455 | * Parameters:
456 | *
457 | * body - An object representing the body.
458 | *
459 | * If you expect the 'body' to come from a form, this typically means that
460 | * it would be a flat object of properties and values, as in:
461 | *
462 | * > { name: 'Howard Abrams',
463 | * > age: 522
464 | * > }
465 | *
466 | * If the client is expecting a JSON object through a REST interface, then
467 | * this object could be anything.
468 | */
469 | mockRequest._setBody = function _setBody(body) {
470 | mockRequest.body = body;
471 | };
472 |
473 | /**
474 | * Function: _addBody
475 | *
476 | * Adds another body parameter the client gets when calling the 'body'
477 | * parameter with another property value, e.g. the name of a form element
478 | * that was passed in.
479 | *
480 | * Parameters:
481 | *
482 | * key - The key. For instance, 'bob' would be accessed: request.params.bob
483 | * value - The value to return when accessed.
484 | */
485 | mockRequest._addBody = function _addBody(key, value) {
486 | mockRequest.body[key] = value;
487 | };
488 |
489 | /**
490 | * Function: send
491 | *
492 | * Write data to the request stream which will trigger request's 'data', and 'end' event
493 | *
494 | * Parameters:
495 | *
496 | * data - string, array, object, number, buffer
497 | */
498 | mockRequest.send = function send(data) {
499 | if (Buffer.isBuffer(data)) {
500 | this.emit('data', data);
501 | } else if (typeof data === 'object' || typeof data === 'number') {
502 | this.emit('data', Buffer.from(JSON.stringify(data)));
503 | } else if (typeof data === 'string') {
504 | this.emit('data', Buffer.from(data));
505 | }
506 | this.emit('end');
507 | };
508 |
509 | /**
510 | * Function: hostname
511 | *
512 | * If Hostname is not set explicitly, then derive it from the Host header without port information
513 | *
514 | */
515 | if (!mockRequest.hostname) {
516 | mockRequest.hostname = (function getHostname() {
517 | if (!mockRequest.headers.host) {
518 | return '';
519 | }
520 |
521 | const hostname = mockRequest.headers.host.split(':')[0].split('.');
522 | return hostname.join('.');
523 | })();
524 | }
525 |
526 | /**
527 | * Function: subdomains
528 | *
529 | * Subdomains are the dot-separated parts of the host before the main domain of the app.
530 | *
531 | */
532 | mockRequest.subdomains = (function getSubdomains() {
533 | if (!mockRequest.headers.host) {
534 | return [];
535 | }
536 |
537 | const offset = 2;
538 | const subdomains = mockRequest.headers.host.split('.').reverse();
539 |
540 | return subdomains.slice(offset);
541 | })();
542 |
543 | /**
544 | * Function: asyncIterator
545 | *
546 | * Buffers data, error, end, and close events and yields them in order.
547 | * Unlike stream.Readable, this async iterator implementation will not exit
548 | * early on error or close.
549 | */
550 | mockRequest[Symbol.asyncIterator] = async function* asyncIterator() {
551 | let ended = false;
552 | let closed = false;
553 | let error = null;
554 | const chunks = [];
555 | let resolvePromise = null;
556 |
557 | const promiseExecutor = (resolve) => {
558 | resolvePromise = resolve;
559 | };
560 |
561 | const promiseResolver = () => {
562 | if (resolvePromise) {
563 | resolvePromise();
564 | resolvePromise = null;
565 | }
566 | };
567 | const dataEventHandler = (chunk) => {
568 | if (ended || closed || error) {
569 | return;
570 | }
571 | chunks.push(chunk);
572 | promiseResolver();
573 | };
574 | const endEventHandler = () => {
575 | if (ended || closed || error) {
576 | return;
577 | }
578 | ended = true;
579 | promiseResolver();
580 | };
581 | const closeEventHandler = () => {
582 | if (closed || error) {
583 | return;
584 | }
585 | closed = true;
586 | promiseResolver();
587 | };
588 | const errorEventHandler = (err) => {
589 | if (closed || error) {
590 | return;
591 | }
592 | error = err;
593 | promiseResolver();
594 | };
595 |
596 | mockRequest.on('data', dataEventHandler);
597 | mockRequest.on('end', endEventHandler);
598 | mockRequest.on('close', closeEventHandler);
599 | mockRequest.on('error', errorEventHandler);
600 |
601 | // Emit custom event after entering the loop.
602 | setTimeout(() => {
603 | this.emit('async_iterator');
604 | });
605 |
606 | try {
607 | for (;;) {
608 | // eslint-disable-next-line no-await-in-loop
609 | await new Promise(promiseExecutor);
610 | let i = 0;
611 | for (;;) {
612 | if (error) {
613 | throw error;
614 | }
615 | if (closed) {
616 | return;
617 | }
618 |
619 | const hasChunks = i < chunks.length;
620 | if (!hasChunks) {
621 | if (ended) {
622 | // End signaled. Bail.
623 | return;
624 | }
625 | // Wait for next push.
626 | break;
627 | }
628 |
629 | const chunk = chunks[i];
630 | chunks[i] = undefined;
631 | i += 1;
632 | yield chunk;
633 | }
634 | chunks.length = 0;
635 | }
636 | } finally {
637 | chunks.length = 0;
638 | error = null;
639 |
640 | mockRequest.off('data', dataEventHandler);
641 | mockRequest.off('end', endEventHandler);
642 | mockRequest.off('close', closeEventHandler);
643 | mockRequest.off('error', errorEventHandler);
644 | }
645 | };
646 |
647 | return mockRequest;
648 | }
649 |
650 | module.exports.createRequest = createRequest;
651 |
--------------------------------------------------------------------------------
/lib/mockResponse.js:
--------------------------------------------------------------------------------
1 | /**
2 | * File: mockResponse
3 | *
4 | * This file implements node.js's implementation of a 'response' object.
5 | * Like all good mocks, the response file that can be called and used in
6 | * place of a real HTTP response object.
7 | *
8 | * @author Howard Abrams
9 | */
10 |
11 | /**
12 | * Function: createResponse
13 | *
14 | * Creates a new mock 'response' instance. All values are reset to the
15 | * defaults.
16 | *
17 | * Parameters:
18 | *
19 | * options - An object of named parameters.
20 | *
21 | * Options:
22 | *
23 | * encoding - The default encoding for the response
24 | */
25 |
26 | const mime = require('mime');
27 | const path = require('path');
28 | const contentDisposition = require('content-disposition');
29 | let WritableStream = require('./mockWritableStream');
30 | let EventEmitter = require('./mockEventEmitter');
31 | const http = require('./node/http');
32 | const utils = require('./utils');
33 |
34 | const TypedArray = Object.getPrototypeOf(Uint8Array);
35 |
36 | function createResponse(options = {}) {
37 | let _endCalled = false;
38 | let _data = '';
39 | let _buffer = Buffer.alloc(0);
40 | const _chunks = [];
41 | let _size = 0;
42 | let _encoding = options.encoding;
43 |
44 | let _redirectUrl = '';
45 | let _renderView = '';
46 | let _renderData = {};
47 |
48 | if (options.writableStream) {
49 | WritableStream = options.writableStream;
50 | }
51 | if (options.eventEmitter) {
52 | EventEmitter = options.eventEmitter;
53 | }
54 | const writableStream = new WritableStream();
55 |
56 | const mockRequest = options.req;
57 | // create mockResponse
58 |
59 | const mockResponse = Object.create(EventEmitter.prototype);
60 | EventEmitter.call(mockResponse);
61 |
62 | mockResponse._headers = {};
63 |
64 | mockResponse.cookies = {};
65 | mockResponse.finished = false;
66 | mockResponse.writableEnded = false;
67 | mockResponse.writableFinished = false;
68 | mockResponse.headersSent = false;
69 | mockResponse.statusCode = 200;
70 | mockResponse.statusMessage = 'OK';
71 |
72 | // http://expressjs.com/en/api.html#res.locals
73 | mockResponse.locals = options.locals || {};
74 |
75 | mockResponse.cookie = function cookie(name, value, opt) {
76 | mockResponse.cookies[name] = {
77 | value,
78 | options: opt
79 | };
80 |
81 | return this;
82 | };
83 |
84 | mockResponse.clearCookie = function clearCookie(name, opt) {
85 | const opts = opt || {};
86 | opts.expires = new Date(1);
87 | opts.path = '/';
88 |
89 | return this.cookie(name, '', opts);
90 | };
91 |
92 | mockResponse.status = function status(code) {
93 | mockResponse.statusCode = code;
94 | return this;
95 | };
96 |
97 | /**
98 | * Function: writeHead
99 | *
100 | * The 'writeHead' function from node's HTTP API.
101 | *
102 | * Parameters:
103 | *
104 | * statusCode - A number to send as a the HTTP status
105 | * headers - An object of properties that will be used for
106 | * the HTTP headers.
107 | */
108 | mockResponse.writeHead = function writeHead(statusCode, statusMessage, headers) {
109 | if (_endCalled) {
110 | throw new Error('The end() method has already been called.');
111 | }
112 |
113 | if (mockResponse.headersSent) {
114 | // Node docs: "This method must only be called once on a message"
115 | // but it doesn't error if you do call it after first chunk of body is sent
116 | // so we shouldn't throw here either (although it's a bug in the code).
117 | // We return without updating since in real life it's just possible the double call didn't
118 | // completely corrupt the response (for example not using chunked encoding due to HTTP/1.0 client)
119 | // and in this case the client will see the _original_ headers.
120 | return this;
121 | }
122 |
123 | mockResponse.statusCode = statusCode;
124 |
125 | // resolve statusMessage and headers as optional
126 | if (Object.prototype.toString.call(statusMessage) === '[object Object]') {
127 | // eslint-disable-next-line no-param-reassign
128 | headers = statusMessage;
129 | // eslint-disable-next-line no-param-reassign
130 | statusMessage = null;
131 | }
132 |
133 | if (statusMessage) {
134 | mockResponse.statusMessage = statusMessage;
135 | }
136 |
137 | // The headers specified earlier (been set with `mockResponse.setHeader`)
138 | // should not be overwritten but be merged with the headers
139 | // passed into `mockResponse.writeHead`.
140 | if (headers) {
141 | Object.assign(mockResponse._headers, utils.convertKeysToLowerCase(headers));
142 | }
143 |
144 | this.headersSent = true;
145 | return this;
146 | };
147 |
148 | /**
149 | * The 'send' function from restify's Response API that returns data
150 | * to the client. Can be called multiple times.
151 | *
152 | * @see http://mcavage.me/node-restify/#response-api
153 | *
154 | * @param data The data to return. Must be a string.
155 | */
156 | mockResponse.send = function send(a, b, c) {
157 | const _formatData = (data) => {
158 | if (typeof data === 'object') {
159 | if (data.statusCode) {
160 | mockResponse.statusCode = data.statusCode;
161 | } else if (data.httpCode) {
162 | mockResponse.statusCode = data.httpCode;
163 | }
164 |
165 | if (data.body) {
166 | _data = data.body;
167 | } else {
168 | _data = data;
169 | }
170 | } else {
171 | _data += data ?? '';
172 | }
173 | };
174 |
175 | switch (arguments.length) {
176 | case 1:
177 | if (typeof a === 'number') {
178 | mockResponse.statusCode = a;
179 | } else {
180 | _formatData(a);
181 | }
182 | break;
183 |
184 | case 2:
185 | if (typeof a === 'number') {
186 | _formatData(b);
187 | mockResponse.statusCode = a;
188 | } else if (typeof b === 'number') {
189 | _formatData(a);
190 | mockResponse.statusCode = b;
191 | console.warn('WARNING: Called send() with deprecated parameter order');
192 | } else {
193 | _formatData(a);
194 | _encoding = b;
195 | }
196 | break;
197 |
198 | case 3:
199 | _formatData(a);
200 | mockResponse._headers = utils.convertKeysToLowerCase(b);
201 | mockResponse.statusCode = c;
202 | console.warn('WARNING: Called send() with deprecated three parameters');
203 | break;
204 |
205 | default:
206 | break;
207 | }
208 |
209 | mockResponse.headersSent = true;
210 |
211 | mockResponse.emit('send');
212 | mockResponse.end();
213 |
214 | return mockResponse;
215 | };
216 |
217 | /**
218 | * Send given HTTP status code.
219 | *
220 | * Sets the response status to `statusCode` and the body of the
221 | * response to the standard description from node's http.STATUS_CODES
222 | * or the statusCode number if no description.
223 | *
224 | * Examples:
225 | *
226 | * mockResponse.sendStatus(200);
227 | *
228 | * @param {number} statusCode
229 | * @api public
230 | */
231 |
232 | mockResponse.sendStatus = function sendStatus(statusCode) {
233 | const body = http.STATUS_CODES[statusCode] || String(statusCode);
234 |
235 | mockResponse.statusCode = statusCode;
236 | mockResponse.type('txt');
237 |
238 | return mockResponse.send(body);
239 | };
240 |
241 | /**
242 | * Function: json
243 | *
244 | * The 'json' function from node's HTTP API that returns JSON
245 | * data to the client.
246 | *
247 | * Parameters:
248 | *
249 | * a - Either a statusCode or string containing JSON payload
250 | * b - Either a statusCode or string containing JSON payload
251 | *
252 | * If not specified, the statusCode defaults to 200.
253 | * Second parameter is optional.
254 | */
255 | mockResponse.json = function json(a, b) {
256 | mockResponse.setHeader('Content-Type', 'application/json');
257 | if (typeof a !== 'undefined') {
258 | if (typeof a === 'number' && typeof b !== 'undefined') {
259 | mockResponse.statusCode = a;
260 | mockResponse.write(JSON.stringify(b), 'utf8');
261 | } else if (typeof b !== 'undefined' && typeof b === 'number') {
262 | mockResponse.statusCode = b;
263 | mockResponse.write(JSON.stringify(a), 'utf8');
264 | } else {
265 | mockResponse.write(JSON.stringify(a), 'utf8');
266 | }
267 | }
268 | mockResponse.emit('send');
269 | mockResponse.end();
270 |
271 | return mockResponse;
272 | };
273 |
274 | /**
275 | * Function: jsonp
276 | *
277 | * The 'jsonp' function from node's HTTP API that returns JSON
278 | * data to the client.
279 | *
280 | * Parameters:
281 | *
282 | * a - Either a statusCode or string containing JSON payload
283 | * b - Either a statusCode or string containing JSON payload
284 | *
285 | * If not specified, the statusCode defaults to 200.
286 | * Second parameter is optional.
287 | */
288 | mockResponse.jsonp = function jsonp(a, b) {
289 | mockResponse.setHeader('Content-Type', 'text/javascript');
290 | if (typeof a !== 'undefined') {
291 | if (typeof a === 'number' && typeof b !== 'undefined') {
292 | mockResponse.statusCode = a;
293 | _data += JSON.stringify(b);
294 | } else if (typeof b !== 'undefined' && typeof b === 'number') {
295 | mockResponse.statusCode = b;
296 | _data += JSON.stringify(a);
297 | } else {
298 | _data += JSON.stringify(a);
299 | }
300 | }
301 | mockResponse.emit('send');
302 | mockResponse.end();
303 |
304 | return mockResponse;
305 | };
306 |
307 | /**
308 | * Set "Content-Type" response header with `type` through `mime.lookup()`
309 | * when it does not contain "/", or set the Content-Type to `type` otherwise.
310 | *
311 | * Examples:
312 | *
313 | * res.type('.html');
314 | * res.type('html');
315 | * res.type('json');
316 | * res.type('application/json');
317 | * res.type('png');
318 | *
319 | * @param {String} type
320 | * @return {ServerResponse} for chaining
321 | * @api public
322 | */
323 | mockResponse.contentType = function contentType(type) {
324 | return mockResponse.set('Content-Type', type.indexOf('/') >= 0 ? type : mime.lookup(type));
325 | };
326 | mockResponse.type = mockResponse.contentType;
327 |
328 | /**
329 | * Set 'Location' response header.
330 | *
331 | * @see http://expressjs.com/en/api.html#res.location
332 | *
333 | * @param {String} location The location to set in the header.
334 | * @return {ServerResponse} For chaining
335 | */
336 | mockResponse.location = function setLocation(location) {
337 | return mockResponse.set('Location', location);
338 | };
339 |
340 | /**
341 | * Function: write
342 | *
343 | * This function has the same behavior as the 'send' function.
344 | *
345 | * Parameters:
346 | *
347 | * data - The data to return. Must be a string. Appended to
348 | * previous calls to data.
349 | * encoding - Optional encoding value.
350 | */
351 |
352 | mockResponse.write = function write(data, encoding) {
353 | mockResponse.headersSent = true;
354 |
355 | if (data instanceof Buffer) {
356 | _chunks.push(data);
357 | _size += data.length;
358 | } else if (data instanceof TypedArray) {
359 | _data += new TextDecoder(encoding).decode(data);
360 | } else {
361 | _data += data;
362 | }
363 |
364 | if (encoding) {
365 | _encoding = encoding;
366 | }
367 | };
368 |
369 | /**
370 | * Function: getEndArguments
371 | *
372 | * Utility function that parses and names parameters for the various
373 | * mockResponse.end() signatures. Reference:
374 | * https://nodejs.org/api/http.html#http_response_end_data_encoding_callback
375 | *
376 | */
377 | function getEndArguments(args) {
378 | let data;
379 | let encoding;
380 | let callback;
381 | if (args[0]) {
382 | if (typeof args[0] === 'function') {
383 | callback = args[0];
384 | } else {
385 | data = args[0];
386 | }
387 | }
388 | if (args[1]) {
389 | const type = typeof args[1];
390 | if (type === 'function') {
391 | callback = args[1];
392 | } else if (type === 'string' || args[1] instanceof String) {
393 | encoding = args[1];
394 | }
395 | }
396 | if (args[2] && typeof args[2] === 'function') {
397 | callback = args[2];
398 | }
399 | return { data, encoding, callback };
400 | }
401 |
402 | /**
403 | * Function: end
404 | *
405 | * The 'end' function from node's HTTP API that finishes
406 | * the connection request. This must be called.
407 | *
408 | * Signature: response.end([data[, encoding]][, callback])
409 | *
410 | * Parameters:
411 | *
412 | * data - Optional data to return. Must be a string or Buffer instance.
413 | * Appended to previous calls to .
414 | * encoding - Optional encoding value.
415 | * callback - Optional callback function, called once the logic has run
416 | *
417 | */
418 | mockResponse.end = function end(...endArgs) {
419 | if (_endCalled) {
420 | // Do not emit this event twice.
421 | return;
422 | }
423 |
424 | mockResponse.finished = true;
425 | mockResponse.writableEnded = true;
426 | mockResponse.headersSent = true;
427 |
428 | _endCalled = true;
429 |
430 | const args = getEndArguments(endArgs);
431 |
432 | if (args.data) {
433 | if (args.data instanceof Buffer) {
434 | _chunks.push(args.data);
435 | _size += args.data.length;
436 | } else if (args.data instanceof TypedArray) {
437 | _data += new TextDecoder(args.encoding).decode(args.data);
438 | } else {
439 | _data += args.data;
440 | }
441 | }
442 |
443 | if (_chunks.length) {
444 | switch (_chunks.length) {
445 | case 1:
446 | _buffer = _chunks[0];
447 | break;
448 | default:
449 | _buffer = Buffer.alloc(_size);
450 | for (let i = 0, pos = 0, l = _chunks.length; i < l; i++) {
451 | const chunk = _chunks[i];
452 | chunk.copy(_buffer, pos);
453 | pos += chunk.length;
454 | }
455 | break;
456 | }
457 | }
458 |
459 | if (args.encoding) {
460 | _encoding = args.encoding;
461 | }
462 |
463 | mockResponse.emit('end');
464 | mockResponse.writableFinished = true; // Reference: https://nodejs.org/docs/latest-v12.x/api/http.html#http_request_writablefinished
465 | mockResponse.emit('finish');
466 |
467 | if (args.callback) {
468 | args.callback();
469 | }
470 | };
471 |
472 | /**
473 | * Function: vary
474 | *
475 | * Adds the field/s to the Vary response header
476 | *
477 | * Examples:
478 | *
479 | * res.vary('A-B-Test');
480 | * res.vary(['A-B-Test', 'Known-User']);
481 | */
482 | mockResponse.vary = function vary(fields) {
483 | const header = mockResponse.getHeader('Vary') || '';
484 | let values = header.length ? header.split(', ') : [];
485 |
486 | const uniqueFields = (Array.isArray(fields) ? fields : [fields]).filter((field) => {
487 | const regex = new RegExp(field, 'i');
488 |
489 | const matches = values.filter((value) => value.match(regex));
490 |
491 | return !matches.length;
492 | });
493 |
494 | values = values.concat(uniqueFields);
495 |
496 | return mockResponse.setHeader('Vary', values.join(', '));
497 | };
498 |
499 | /**
500 | * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
501 | *
502 | * Example:
503 | *
504 | * res.attachment('download.csv')
505 | *
506 | * @param {String} filename
507 | * @return {ServerResponse}
508 | * @api public
509 | */
510 |
511 | mockResponse.attachment = function attachment(filename) {
512 | if (filename) {
513 | mockResponse.type(path.extname(filename));
514 | }
515 |
516 | mockResponse.set('Content-Disposition', contentDisposition(filename));
517 |
518 | return this;
519 | };
520 |
521 | /**
522 | * Append additional header `field` with value `val`.
523 | *
524 | * Example:
525 | *
526 | * res.append('Link', ['', '']);
527 | * res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
528 | * res.append('Warning', '199 Miscellaneous warning');
529 | *
530 | * @param {String} field
531 | * @param {String|Array} val
532 | * @return {ServerResponse} for chaining
533 | * @api public
534 | */
535 | mockResponse.append = function append(field, val) {
536 | const prev = mockResponse.get(field);
537 | let value = val;
538 |
539 | if (prev) {
540 | // concat the new and prev vals
541 | value = Array.isArray(prev) ? prev.concat(val) : [prev].concat(val);
542 | }
543 |
544 | return mockResponse.set(field, value);
545 | };
546 |
547 | /**
548 | * Set header `field` to `val`, or pass
549 | * an object of header fields.
550 | *
551 | * Examples:
552 | *
553 | * res.set('Foo', ['bar', 'baz']);
554 | * res.set('Accept', 'application/json');
555 | * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
556 | *
557 | * Aliased as `mockResponse.header()`.
558 | *
559 | * @param {String|Object|Array} field
560 | * @param {String} val
561 | * @return {ServerResponse} for chaining
562 | * @api public
563 | */
564 | mockResponse.header = function header(field, val) {
565 | if (arguments.length === 2) {
566 | let value;
567 | if (Array.isArray(val)) {
568 | value = val.map(String);
569 | } else {
570 | value = String(val);
571 | }
572 | mockResponse.setHeader(field, value);
573 | } else if (typeof field === 'string') {
574 | return mockResponse.getHeader(field);
575 | } else {
576 | for (const key in field) {
577 | if ({}.hasOwnProperty.call(field, key)) {
578 | mockResponse.setHeader(key, field[key]);
579 | }
580 | }
581 | }
582 | return mockResponse;
583 | };
584 | mockResponse.set = mockResponse.header;
585 |
586 | /**
587 | * Function: getHeaders
588 | *
589 | * Returns a shallow copy of the current outgoing headers.
590 | */
591 | mockResponse.getHeaders = function getHeaders() {
592 | return JSON.parse(JSON.stringify(mockResponse._headers));
593 | };
594 |
595 | /**
596 | * Function: getHeader
597 | * Function: get
598 | *
599 | * Returns a particular header by name.
600 | */
601 | mockResponse.getHeader = function getHeader(name) {
602 | return mockResponse._headers[name.toLowerCase()];
603 | };
604 | mockResponse.get = mockResponse.getHeader;
605 |
606 | /**
607 | * Function: getHeaderNames
608 | *
609 | * Returns an array containing the unique names of the current outgoing headers.
610 | */
611 | mockResponse.getHeaderNames = function getHeaderNames() {
612 | return Object.keys(mockResponse._headers); // names are already stored in lowercase
613 | };
614 |
615 | /**
616 | * Function: hasHeader
617 | *
618 | * Returns `true` if the header identified by `name` is currently set.
619 | */
620 | mockResponse.hasHeader = function hasHeader(name) {
621 | return name.toLowerCase() in mockResponse._headers;
622 | };
623 |
624 | /**
625 | * Function: setHeader
626 | * Function: set
627 | *
628 | * Set a particular header by name.
629 | */
630 | mockResponse.setHeader = function setHeader(name, value) {
631 | mockResponse._headers[name.toLowerCase()] = value;
632 | return this;
633 | };
634 |
635 | /**
636 | * Function: appendHeader
637 | *
638 | * Append a header by name. If a header already exists, the new value is appended to the existing header.
639 | */
640 | mockResponse.appendHeader = function appendHeader(name, value) {
641 | mockResponse.append(name, value);
642 | return this;
643 | };
644 |
645 | /**
646 | * Function: removeHeader
647 | *
648 | * Removes an HTTP header by name.
649 | */
650 | mockResponse.removeHeader = function removeHeader(name) {
651 | delete mockResponse._headers[name.toLowerCase()];
652 | };
653 |
654 | /**
655 | * Function: setEncoding
656 | *
657 | * Sets the encoding for the data. Generally 'utf8'.
658 | *
659 | * Parameters:
660 | *
661 | * encoding - The string representing the encoding value.
662 | */
663 | mockResponse.setEncoding = function setEncoding(encoding) {
664 | _encoding = encoding;
665 | };
666 |
667 | mockResponse.getEncoding = function getEncoding() {
668 | return _encoding;
669 | };
670 |
671 | /**
672 | * Function: redirect
673 | *
674 | * Redirect to a url with response code
675 | */
676 | mockResponse.redirect = function redirect(a, b) {
677 | switch (arguments.length) {
678 | case 1:
679 | mockResponse.statusCode = 302;
680 | _redirectUrl = a;
681 | break;
682 |
683 | case 2:
684 | if (typeof a === 'number') {
685 | mockResponse.statusCode = a;
686 | _redirectUrl = b;
687 | }
688 | break;
689 |
690 | default:
691 | break;
692 | }
693 | mockResponse.end();
694 | };
695 |
696 | /**
697 | * Function: render
698 | *
699 | * Render a view with a callback responding with the
700 | * rendered string.
701 | */
702 | mockResponse.render = function render(a, b, c) {
703 | _renderView = a;
704 |
705 | let data = b;
706 | let done = c;
707 |
708 | // support callback function as second arg
709 | if (typeof b === 'function') {
710 | done = b;
711 | data = {};
712 | }
713 |
714 | switch (arguments.length) {
715 | case 2:
716 | case 3:
717 | _renderData = data;
718 | break;
719 |
720 | default:
721 | break;
722 | }
723 |
724 | if (typeof done === 'function') {
725 | done(null, '');
726 | } else {
727 | mockResponse.emit('render');
728 | mockResponse.end();
729 | }
730 | };
731 |
732 | /**
733 | * Chooses the correct response function from the given supported types.
734 | * This method requires that the mockResponse object be initialized with a
735 | * mockRequest object reference, otherwise it will throw. @see createMocks.
736 | *
737 | * @param {Object} supported Object with formats to handler functions.
738 | * @return {Object} Whatever handler function returns.
739 | */
740 | mockResponse.format = function format(supported = {}) {
741 | const types = Object.keys(supported);
742 |
743 | if (types.length === 0) {
744 | return mockResponse.sendStatus(406);
745 | }
746 |
747 | if (!mockRequest) {
748 | throw new Error(
749 | 'Request object unavailable. Use createMocks or pass in a request object in createResponse to use format.'
750 | );
751 | }
752 |
753 | const accepted = mockRequest.accepts(types);
754 |
755 | if (accepted) {
756 | return supported[accepted]();
757 | }
758 |
759 | if (supported.default) {
760 | return supported.default();
761 | }
762 |
763 | return mockResponse.sendStatus(406);
764 | };
765 |
766 | // WritableStream.writable is not a function
767 | // mockResponse.writable = function() {
768 | // return writableStream.writable.apply(this, arguments);
769 | // };
770 |
771 | // mockResponse.end = function(){
772 | // return writableStream.end.apply(this, arguments);
773 | // };
774 |
775 | mockResponse.destroy = function destroy(...args) {
776 | return writableStream.destroy.apply(this, args);
777 | };
778 |
779 | mockResponse.destroySoon = function destroySoon(...args) {
780 | return writableStream.destroySoon.apply(this, args);
781 | };
782 |
783 | // This mock object stores some state as well
784 | // as some test-analysis functions:
785 |
786 | /**
787 | * Function: _isEndCalled
788 | *
789 | * Since the function must be called, this function
790 | * returns true if it has been called. False otherwise.
791 | */
792 | mockResponse._isEndCalled = function _isEndCalled() {
793 | return _endCalled;
794 | };
795 |
796 | /**
797 | * Function: _getHeaders
798 | *
799 | * Returns all the headers that were set. This may be an
800 | * empty object, but probably will have "Content-Type" set.
801 | */
802 | mockResponse._getHeaders = function _getHeaders() {
803 | return mockResponse._headers;
804 | };
805 |
806 | /**
807 | * Function: _getLocals
808 | *
809 | * Returns all the locals that were set.
810 | */
811 | mockResponse._getLocals = function _getLocals() {
812 | return mockResponse.locals;
813 | };
814 |
815 | /**
816 | * Function: _getData
817 | *
818 | * The data sent to the user.
819 | */
820 | mockResponse._getData = function _getData() {
821 | return _data;
822 | };
823 |
824 | /**
825 | * Function: _getJSONData
826 | *
827 | * The data sent to the user as JSON.
828 | */
829 | mockResponse._getJSONData = function _getJSONData() {
830 | return JSON.parse(_data);
831 | };
832 |
833 | /**
834 | * Function: _getBuffer
835 | *
836 | * The buffer containing data to be sent to the user.
837 | * Non-empty if Buffers were given in calls to write() and end()
838 | */
839 | mockResponse._getBuffer = function _getBuffer() {
840 | return _buffer;
841 | };
842 |
843 | /**
844 | * Function: _getChunks
845 | *
846 | * The buffer containing data to be sent to the user.
847 | * Non-empty if Buffers were given in calls to write() and end()
848 | */
849 | mockResponse._getChunks = function _getChunks() {
850 | return _chunks;
851 | };
852 |
853 | /**
854 | * Function: _getStatusCode
855 | *
856 | * The status code that was sent to the user.
857 | */
858 | mockResponse._getStatusCode = function _getStatusCode() {
859 | return mockResponse.statusCode;
860 | };
861 |
862 | /**
863 | * Function: _getStatusMessage
864 | *
865 | * The status message that was sent to the user.
866 | */
867 | mockResponse._getStatusMessage = function _getStatusMessage() {
868 | return mockResponse.statusMessage;
869 | };
870 |
871 | /**
872 | * Function: _isJSON
873 | *
874 | * Returns true if the data sent was defined as JSON.
875 | * It doesn't validate the data that was sent.
876 | */
877 | mockResponse._isJSON = function _isJSON() {
878 | return mockResponse.getHeader('Content-Type') === 'application/json';
879 | };
880 |
881 | /**
882 | * Function: _isUTF8
883 | *
884 | * If the encoding was set, and it was set to UTF-8, then
885 | * this function return true. False otherwise.
886 | *
887 | * Returns:
888 | *
889 | * False if the encoding wasn't set and wasn't set to "utf8".
890 | */
891 | mockResponse._isUTF8 = function _isUTF8() {
892 | if (!_encoding) {
893 | return false;
894 | }
895 | return _encoding === 'utf8';
896 | };
897 |
898 | /**
899 | * Function: _isDataLengthValid
900 | *
901 | * If the Content-Length header was set, this will only
902 | * return true if the length is actually the length of the
903 | * data that was set.
904 | *
905 | * Returns:
906 | *
907 | * True if the "Content-Length" header was not
908 | * set. Otherwise, it compares it.
909 | */
910 | mockResponse._isDataLengthValid = function _isDataLengthValid() {
911 | if (mockResponse.getHeader('Content-Length')) {
912 | return mockResponse.getHeader('Content-Length').toString() === _data.length.toString();
913 | }
914 | return true;
915 | };
916 |
917 | /**
918 | * Function: _getRedirectUrl
919 | *
920 | * Return redirect url of redirect method
921 | *
922 | * Returns:
923 | *
924 | * Redirect url
925 | */
926 | mockResponse._getRedirectUrl = function _getRedirectUrl() {
927 | return _redirectUrl;
928 | };
929 |
930 | /**
931 | * Function: _getRenderView
932 | *
933 | * Return render view of render method
934 | *
935 | * Returns:
936 | *
937 | * render view
938 | */
939 | mockResponse._getRenderView = function _getRenderView() {
940 | return _renderView;
941 | };
942 |
943 | /**
944 | * Function: _getRenderData
945 | *
946 | * Return render data of render method
947 | *
948 | * Returns:
949 | *
950 | * render data
951 | */
952 | mockResponse._getRenderData = function _getRenderData() {
953 | return _renderData;
954 | };
955 |
956 | return mockResponse;
957 | }
958 |
959 | module.exports.createResponse = createResponse;
960 |
--------------------------------------------------------------------------------
/lib/mockWritableStream.js:
--------------------------------------------------------------------------------
1 | /*
2 | * http://nodejs.org/api/stream.html#stream_writable_stream
3 | */
4 |
5 | function WritableStream() {}
6 |
7 | Object.defineProperty(WritableStream, 'writable', {
8 | configurable: true,
9 | enumerable: true,
10 | get() {
11 | return true;
12 | }
13 | });
14 |
15 | // WritableStream.prototype.write = function(string, [encoding], [fd]){}
16 | // WritableStream.prototype.write = function(buffer){}
17 | WritableStream.prototype.end = () => {};
18 | // WritableStream.prototype.end = function(string, encoding){}
19 | // WritableStream.prototype.end = function(buffer){}
20 | WritableStream.prototype.destroy = () => {};
21 | WritableStream.prototype.destroySoon = () => {};
22 |
23 | module.exports = WritableStream;
24 |
--------------------------------------------------------------------------------
/lib/node/_http_incoming.js:
--------------------------------------------------------------------------------
1 | const util = require('util');
2 | const Stream = require('stream');
3 |
4 | function readStart() {}
5 | exports.readStart = readStart;
6 |
7 | function readStop() {}
8 | exports.readStop = readStop;
9 |
10 | function IncomingMessage() {
11 | Stream.Readable.call(this);
12 |
13 | this.httpVersionMajor = null;
14 | this.httpVersionMinor = null;
15 | this.httpVersion = null;
16 | this.complete = false;
17 | this.headers = {};
18 | this.rawHeaders = [];
19 | this.trailers = {};
20 | this.rawTrailers = [];
21 |
22 | this.readable = true;
23 |
24 | this._pendings = [];
25 | this._pendingIndex = 0;
26 | this.upgrade = null;
27 |
28 | this.url = '';
29 | this.method = null;
30 |
31 | this.statusCode = null;
32 | this.statusMessage = null;
33 |
34 | this._consuming = false;
35 |
36 | this._dumped = false;
37 | }
38 | util.inherits(IncomingMessage, Stream.Readable);
39 |
40 | exports.IncomingMessage = IncomingMessage;
41 |
42 | IncomingMessage.prototype.read = () => {};
43 | IncomingMessage.prototype._read = () => {};
44 | IncomingMessage.prototype.destroy = () => {};
45 |
46 | IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
47 | if (callback) {
48 | setTimeout(callback, msecs);
49 | }
50 | };
51 |
52 | IncomingMessage.prototype._addHeaderLines = function _addHeaderLines(headers, n) {
53 | if (headers && headers.length) {
54 | let raw;
55 | let dest;
56 | if (this.complete) {
57 | raw = this.rawTrailers;
58 | dest = this.trailers;
59 | } else {
60 | raw = this.rawHeaders;
61 | dest = this.headers;
62 | }
63 |
64 | for (let i = 0; i < n; i += 2) {
65 | const k = headers[i];
66 | const v = headers[i + 1];
67 | raw.push(k);
68 | raw.push(v);
69 | this._addHeaderLine(k, v, dest);
70 | }
71 | }
72 | };
73 |
74 | IncomingMessage.prototype._addHeaderLine = function _addHeaderLine(field, value, dest) {
75 | const fieldName = field.toLowerCase();
76 | switch (fieldName) {
77 | // Array headers:
78 | case 'set-cookie':
79 | if (!util.isUndefined(dest[fieldName])) {
80 | // eslint-disable-next-line no-param-reassign
81 | dest[fieldName].push(value);
82 | } else {
83 | // eslint-disable-next-line no-param-reassign
84 | dest[fieldName] = [value];
85 | }
86 | break;
87 |
88 | case 'content-type':
89 | case 'content-length':
90 | case 'user-agent':
91 | case 'referer':
92 | case 'host':
93 | case 'authorization':
94 | case 'proxy-authorization':
95 | case 'if-modified-since':
96 | case 'if-unmodified-since':
97 | case 'from':
98 | case 'location':
99 | case 'max-forwards':
100 | if (util.isUndefined(dest[fieldName])) {
101 | // eslint-disable-next-line no-param-reassign
102 | dest[fieldName] = value;
103 | }
104 | break;
105 |
106 | default:
107 | if (!util.isUndefined(dest[fieldName])) {
108 | // eslint-disable-next-line no-param-reassign
109 | dest[fieldName] += `, ${value}`;
110 | } else {
111 | // eslint-disable-next-line no-param-reassign
112 | dest[fieldName] = value;
113 | }
114 | }
115 | };
116 |
117 | IncomingMessage.prototype._dump = function _dump() {
118 | if (!this._dumped) {
119 | this._dumped = true;
120 | }
121 | };
122 |
--------------------------------------------------------------------------------
/lib/node/_http_server.js:
--------------------------------------------------------------------------------
1 | exports.STATUS_CODES = {
2 | 100: 'Continue',
3 | 101: 'Switching Protocols',
4 | 102: 'Processing',
5 | 200: 'OK',
6 | 201: 'Created',
7 | 202: 'Accepted',
8 | 203: 'Non-Authoritative Information',
9 | 204: 'No Content',
10 | 205: 'Reset Content',
11 | 206: 'Partial Content',
12 | 207: 'Multi-Status',
13 | 300: 'Multiple Choices',
14 | 301: 'Moved Permanently',
15 | 302: 'Moved Temporarily',
16 | 303: 'See Other',
17 | 304: 'Not Modified',
18 | 305: 'Use Proxy',
19 | 307: 'Temporary Redirect',
20 | 308: 'Permanent Redirect',
21 | 400: 'Bad Request',
22 | 401: 'Unauthorized',
23 | 402: 'Payment Required',
24 | 403: 'Forbidden',
25 | 404: 'Not Found',
26 | 405: 'Method Not Allowed',
27 | 406: 'Not Acceptable',
28 | 407: 'Proxy Authentication Required',
29 | 408: 'Request Time-out',
30 | 409: 'Conflict',
31 | 410: 'Gone',
32 | 411: 'Length Required',
33 | 412: 'Precondition Failed',
34 | 413: 'Request Entity Too Large',
35 | 414: 'Request-URI Too Large',
36 | 415: 'Unsupported Media Type',
37 | 416: 'Requested Range Not Satisfiable',
38 | 417: 'Expectation Failed',
39 | 418: "I'm a teapot",
40 | 422: 'Unprocessable Entity',
41 | 423: 'Locked',
42 | 424: 'Failed Dependency',
43 | 425: 'Unordered Collection',
44 | 426: 'Upgrade Required',
45 | 428: 'Precondition Required',
46 | 429: 'Too Many Requests',
47 | 431: 'Request Header Fields Too Large',
48 | 500: 'Internal Server Error',
49 | 501: 'Not Implemented',
50 | 502: 'Bad Gateway',
51 | 503: 'Service Unavailable',
52 | 504: 'Gateway Time-out',
53 | 505: 'HTTP Version Not Supported',
54 | 506: 'Variant Also Negotiates',
55 | 507: 'Insufficient Storage',
56 | 509: 'Bandwidth Limit Exceeded',
57 | 510: 'Not Extended',
58 | 511: 'Network Authentication Required'
59 | };
60 |
--------------------------------------------------------------------------------
/lib/node/http.js:
--------------------------------------------------------------------------------
1 | const server = require('./_http_server');
2 |
3 | exports.IncomingMessage = require('./_http_incoming').IncomingMessage;
4 |
5 | exports.STATUS_CODES = server.STATUS_CODES;
6 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | module.exports.convertKeysToLowerCase = (map) => {
2 | const newMap = {};
3 | for (const key in map) {
4 | if ({}.hasOwnProperty.call(map, key)) {
5 | newMap[key.toLowerCase()] = map[key];
6 | }
7 | }
8 | return newMap;
9 | };
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Howard Abrams (http://www.github.com/howardabrams)",
3 | "name": "node-mocks-http",
4 | "description": "Mock 'http' objects for testing Express, Next.js and Koa routing functions",
5 | "version": "1.17.2",
6 | "homepage": "https://github.com/eugef/node-mocks-http",
7 | "bugs": {
8 | "url": "https://github.com/eugef/node-mocks-http/issues"
9 | },
10 | "contributors": [
11 | {
12 | "name": "Howard Abrams",
13 | "email": "howard.abrams@gmail.com",
14 | "url": "https://github.com/howardabrams"
15 | },
16 | {
17 | "name": "Johnny Estilles",
18 | "email": "johnny.estilles@agentia.asia",
19 | "url": "https://github.com/JohnnyEstilles"
20 | },
21 | {
22 | "name": "Eugene Fidelin",
23 | "email": "eugene.fidelin@gmail.com",
24 | "url": "https://github.com/eugef"
25 | }
26 | ],
27 | "license": "MIT",
28 | "keywords": [
29 | "mock",
30 | "stub",
31 | "dummy",
32 | "nodejs",
33 | "js",
34 | "testing",
35 | "test",
36 | "http",
37 | "http mock"
38 | ],
39 | "repository": {
40 | "type": "git",
41 | "url": "git://github.com/eugef/node-mocks-http.git"
42 | },
43 | "main": "./lib/http-mock.js",
44 | "types": "./lib/http-mock.d.ts",
45 | "engines": {
46 | "node": ">=14"
47 | },
48 | "dependencies": {
49 | "accepts": "^1.3.7",
50 | "content-disposition": "^0.5.3",
51 | "depd": "^1.1.0",
52 | "fresh": "^0.5.2",
53 | "merge-descriptors": "^1.0.1",
54 | "methods": "^1.1.2",
55 | "mime": "^1.3.4",
56 | "parseurl": "^1.3.3",
57 | "range-parser": "^1.2.0",
58 | "type-is": "^1.6.18"
59 | },
60 | "peerDependencies": {
61 | "@types/express": "^4.17.21 || ^5.0.0",
62 | "@types/node": "*"
63 | },
64 | "peerDependenciesMeta": {
65 | "@types/express": {
66 | "optional": true
67 | },
68 | "@types/node": {
69 | "optional": true
70 | }
71 | },
72 | "devDependencies": {
73 | "@types/chai": "^4.3.11",
74 | "@types/mocha": "^10.0.6",
75 | "@typescript-eslint/eslint-plugin": "^6.17.0",
76 | "@typescript-eslint/parser": "^6.17.0",
77 | "chai": "^4.2.0",
78 | "eslint": "^8.56.0",
79 | "eslint-config-airbnb-base": "^15.0.0",
80 | "eslint-config-prettier": "^9.1.0",
81 | "eslint-plugin-import": "^2.29.1",
82 | "husky": "^8.0.3",
83 | "mocha": "^10.2.0",
84 | "nyc": "^15.1.0",
85 | "prettier": "^3.1.1",
86 | "sinon": "^17.0.1",
87 | "sinon-chai": "^3.5.0",
88 | "ts-node": "^10.9.1",
89 | "tsd": "^0.29.0",
90 | "typescript": "^5.3.2"
91 | },
92 | "scripts": {
93 | "test": "mocha",
94 | "coverage": "nyc --reporter=html --reporter=lcov --reporter=text-summary mocha",
95 | "format": "prettier --write --list-different .",
96 | "lint": "eslint --fix .",
97 | "check": "npm run check:format && npm run check:lint && npm run check:types",
98 | "check:format": "prettier --list-different .",
99 | "check:lint": "eslint .",
100 | "check:types": "tsd --files ./test/**/*.test-d.ts .",
101 | "postversion": "npm publish && git push --follow-tags",
102 | "prepare": "husky install"
103 | },
104 | "files": [
105 | "lib"
106 | ]
107 | }
108 |
--------------------------------------------------------------------------------
/test/lib/headers.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test: headers.spec.js
3 | *
4 | * This file tests the Headers implementation which provides both
5 | * Express.js style header access and Web API Headers interface functionality.
6 | */
7 | const { expect } = require('chai');
8 | const { createHeaders } = require('../../lib/headers');
9 |
10 | describe('Headers', () => {
11 | describe('Headers basic HTTP', () => {
12 | it('should create an empty headers object', () => {
13 | const headers = createHeaders();
14 | expect(headers).to.be.an('object');
15 | expect(Object.keys(headers).length).to.equal(0);
16 | });
17 |
18 | it('should initialize with provided headers', () => {
19 | const initialHeaders = { 'Content-Type': 'application/json' };
20 | const headers = createHeaders(initialHeaders);
21 |
22 | expect(headers['Content-Type']).to.equal('application/json');
23 | });
24 |
25 | it('should allow to directly access headers', () => {
26 | const headers = createHeaders();
27 | headers['Content-Type'] = 'application/json';
28 |
29 | expect(headers['Content-Type']).to.equal('application/json');
30 | });
31 | });
32 |
33 | describe('Headers Web API Methods', () => {
34 | describe('#get()', () => {
35 | it('should get a header value', () => {
36 | const headers = createHeaders({ 'Content-Type': 'application/json' });
37 |
38 | expect(headers.get('content-type')).to.equal('application/json');
39 | expect(headers.get('Content-Type')).to.equal('application/json');
40 | });
41 |
42 | it('should return undefined for non-existent headers', () => {
43 | const headers = createHeaders();
44 |
45 | expect(headers.get('cContent-Type')).to.be.undefined;
46 | });
47 |
48 | it('should handle the referer/referrer special case', () => {
49 | const headersWithReferer = createHeaders({ referer: 'http://example.com' });
50 | expect(headersWithReferer.get('referrer')).to.equal('http://example.com');
51 |
52 | const headersWithReferrer = createHeaders({ referrer: 'http://example.com' });
53 | expect(headersWithReferrer.get('referer')).to.equal('http://example.com');
54 | });
55 | });
56 |
57 | describe('#getAll()', () => {
58 | it('should get all values for a header as an array', () => {
59 | const headers = createHeaders({ 'Set-Cookie': ['cookie1=value1', 'cookie2=value2'] });
60 |
61 | expect(headers.getAll('Set-Cookie')).to.deep.equal(['cookie1=value1', 'cookie2=value2']);
62 | });
63 |
64 | it('should return a single value as an array', () => {
65 | const headers = createHeaders({ 'Content-Type': 'application/json' });
66 |
67 | expect(headers.getAll('Content-Type')).to.deep.equal(['application/json']);
68 | });
69 |
70 | it('should return an empty array for non-existent headers', () => {
71 | const headers = createHeaders();
72 |
73 | expect(headers.getAll('Content-Type')).to.deep.equal([]);
74 | });
75 | });
76 |
77 | describe('#has()', () => {
78 | it('should check if a header exists', () => {
79 | const headers = createHeaders({ 'Content-Type': 'application/json' });
80 |
81 | expect(headers.has('content-type')).to.be.true;
82 | expect(headers.has('Content-Type')).to.be.true;
83 | });
84 |
85 | it('should return false for non-existent headers', () => {
86 | const headers = createHeaders();
87 |
88 | expect(headers.has('Content-Type')).to.be.false;
89 | });
90 | });
91 |
92 | describe('#set()', () => {
93 | it('should set a header value', () => {
94 | const headers = createHeaders();
95 |
96 | headers.set('Content-Type', 'application/json');
97 | expect(headers['Content-Type']).to.equal('application/json');
98 | });
99 |
100 | it('should overwrite existing headers', () => {
101 | const headers = createHeaders({ 'Content-Type': 'text/html' });
102 |
103 | headers.set('Content-Type', 'application/json');
104 | expect(headers['Content-Type']).to.equal('application/json');
105 | });
106 | });
107 |
108 | describe('#append()', () => {
109 | it('should append a value to a non-existent header', () => {
110 | const headers = createHeaders();
111 |
112 | headers.append('Content-Type', 'application/json');
113 | expect(headers['Content-Type']).to.equal('application/json');
114 | });
115 |
116 | it('should convert a single value to an array when appending', () => {
117 | const headers = createHeaders({ Accept: 'text/html' });
118 |
119 | headers.append('Accept', 'application/json');
120 | expect(headers.Accept).to.deep.equal(['text/html', 'application/json']);
121 | });
122 |
123 | it('should append to an existing array of values', () => {
124 | const headers = createHeaders({ 'Set-Cookie': ['cookie1=value1'] });
125 |
126 | headers.append('Set-Cookie', 'cookie2=value2');
127 | expect(headers['Set-Cookie']).to.deep.equal(['cookie1=value1', 'cookie2=value2']);
128 | });
129 | });
130 |
131 | describe('#delete()', () => {
132 | it('should delete a header', () => {
133 | const headers = createHeaders({ 'Content-Type': 'application/json' });
134 |
135 | headers.delete('Content-Type');
136 | expect(headers['Content-Type']).to.be.undefined;
137 | expect('Content-Type' in headers).to.be.false;
138 | });
139 |
140 | it('should handle case-insensitive deletion', () => {
141 | const headers = createHeaders({ 'Content-Type': 'application/json' });
142 |
143 | headers.delete('Content-Type');
144 | expect('content-type' in headers).to.be.false;
145 | });
146 | });
147 |
148 | describe('#forEach()', () => {
149 | it('should iterate over all headers', () => {
150 | const headers = createHeaders({
151 | 'Content-Type': 'application/json',
152 | Accept: 'text/html',
153 | 'X-Custom': 'custom-value'
154 | });
155 |
156 | const result = {};
157 | headers.forEach((value, key) => {
158 | result[key] = value;
159 | });
160 |
161 | expect(result).to.deep.equal({
162 | 'content-type': 'application/json',
163 | accept: 'text/html',
164 | 'x-custom': 'custom-value'
165 | });
166 | });
167 |
168 | it('should respect thisArg parameter', () => {
169 | const headers = createHeaders({ 'Content-Type': 'application/json' });
170 | const context = { value: 'context' };
171 |
172 | headers.forEach(function iterator() {
173 | expect(this).to.equal(context);
174 | }, context);
175 | });
176 | });
177 | });
178 |
179 | describe('Iterable Interface', () => {
180 | it('should implement entries() iterator', () => {
181 | const headers = createHeaders({
182 | 'Content-Type': 'application/json',
183 | Accept: 'text/html'
184 | });
185 |
186 | const entries = Array.from(headers.entries());
187 | expect(entries).to.deep.include(['content-type', 'application/json']);
188 | expect(entries).to.deep.include(['accept', 'text/html']);
189 | });
190 |
191 | it('should implement keys() iterator', () => {
192 | const headers = createHeaders({
193 | 'Content-Type': 'application/json',
194 | Accept: 'text/html'
195 | });
196 |
197 | const keys = Array.from(headers.keys());
198 | expect(keys).to.include('content-type');
199 | expect(keys).to.include('accept');
200 | });
201 |
202 | it('should implement values() iterator', () => {
203 | const headers = createHeaders({
204 | 'Content-Type': 'application/json',
205 | Accept: 'text/html'
206 | });
207 |
208 | const values = Array.from(headers.values());
209 | expect(values).to.include('application/json');
210 | expect(values).to.include('text/html');
211 | });
212 |
213 | it('should be iterable with Symbol.iterator', () => {
214 | const headers = createHeaders({
215 | 'Content-Type': 'application/json',
216 | Accept: 'text/html'
217 | });
218 |
219 | const entries = Array.from(headers);
220 | expect(entries).to.deep.include(['content-type', 'application/json']);
221 | expect(entries).to.deep.include(['accept', 'text/html']);
222 | });
223 | });
224 |
225 | describe('Property Operations', () => {
226 | it('should delete properties in a case-insensitive manner', () => {
227 | const headers = createHeaders({ 'Content-Type': 'application/json' });
228 |
229 | delete headers['content-type'];
230 | expect('Content-Type' in headers).to.be.false;
231 | });
232 |
233 | it('should list all header keys with Object.keys', () => {
234 | const headers = createHeaders({
235 | 'content-type': 'application/json',
236 | accept: 'text/html'
237 | });
238 |
239 | const keys = Object.keys(headers);
240 | expect(keys).to.include('content-type');
241 | expect(keys).to.include('accept');
242 | expect(keys.length).to.equal(2);
243 | });
244 | });
245 | });
246 |
--------------------------------------------------------------------------------
/test/lib/http-mock.test-d.ts:
--------------------------------------------------------------------------------
1 | import { IncomingMessage as NodeRequest, OutgoingMessage as NodeResponse } from 'http';
2 | // eslint-disable-next-line import/no-unresolved
3 | import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd';
4 | // eslint-disable-next-line import/no-unresolved
5 | import { Request as ExpressRequest, Response as ExpressResponse } from 'express';
6 |
7 | import { createMocks, createRequest, createResponse, MockRequest, MockResponse, Mocks } from '../../lib/http-mock';
8 |
9 | expectType>(createRequest());
10 | expectNotType>(createRequest());
11 | expectAssignable(createRequest());
12 | expectAssignable(createRequest());
13 |
14 | expectType>(createRequest());
15 | expectNotType>(createRequest());
16 | expectAssignable(createRequest());
17 | expectNotAssignable(createRequest());
18 |
19 | expectType>(createResponse());
20 | expectNotType>(createResponse());
21 | expectAssignable(createResponse());
22 | expectAssignable(createResponse());
23 |
24 | expectType>(createResponse());
25 | expectNotType>(createResponse());
26 | expectAssignable(createResponse());
27 | expectNotAssignable(createResponse());
28 |
29 | expectType>(createMocks());
30 | // eslint-disable-next-line no-undef
31 | expectType>(createMocks());
32 |
--------------------------------------------------------------------------------
/test/lib/mockEventEmitter.spec.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai');
2 |
3 | const { expect } = chai;
4 |
5 | const MockEventEmitter = require('../../lib/mockEventEmitter');
6 |
7 | let mockEventEmitter;
8 |
9 | describe('mockEventEmitter', () => {
10 | before(() => {
11 | mockEventEmitter = new MockEventEmitter();
12 | });
13 |
14 | it('should be a function', () => {
15 | expect(MockEventEmitter).to.be.a('function');
16 | });
17 |
18 | it('should be an object factory', () => {
19 | expect(mockEventEmitter).to.be.a('object');
20 | expect(mockEventEmitter).to.be.an.instanceof(MockEventEmitter);
21 | });
22 |
23 | it('should expose "MockEventEmitter" prototype', () => {
24 | expect(mockEventEmitter).to.have.property('addListener');
25 | expect(mockEventEmitter.addListener).to.be.a('function');
26 |
27 | expect(mockEventEmitter).to.have.property('on');
28 | expect(mockEventEmitter.on).to.be.a('function');
29 |
30 | expect(mockEventEmitter).to.have.property('once');
31 | expect(mockEventEmitter.once).to.be.a('function');
32 |
33 | expect(mockEventEmitter).to.have.property('removeListener');
34 | expect(mockEventEmitter.removeListener).to.be.a('function');
35 |
36 | expect(mockEventEmitter).to.have.property('removeAllListeners');
37 | expect(mockEventEmitter.removeAllListeners).to.be.a('function');
38 |
39 | expect(mockEventEmitter).to.have.property('setMaxListeners');
40 | expect(mockEventEmitter.setMaxListeners).to.be.a('function');
41 |
42 | expect(mockEventEmitter).to.have.property('listeners');
43 | expect(mockEventEmitter.listeners).to.be.a('function');
44 |
45 | expect(mockEventEmitter).to.have.property('emit');
46 | expect(mockEventEmitter.emit).to.be.a('function');
47 |
48 | expect(mockEventEmitter).to.have.property('prependListener');
49 | expect(mockEventEmitter.prependListener).to.be.a('function');
50 | });
51 |
52 | it('should return undefined when methods called', () => {
53 | expect(mockEventEmitter.addListener()).to.be.an('undefined');
54 | expect(mockEventEmitter.on()).to.be.an('undefined');
55 | expect(mockEventEmitter.once()).to.be.an('undefined');
56 | expect(mockEventEmitter.removeListener()).to.be.an('undefined');
57 | expect(mockEventEmitter.removeAllListeners()).to.be.an('undefined');
58 | expect(mockEventEmitter.setMaxListeners()).to.be.an('undefined');
59 | expect(mockEventEmitter.listeners()).to.be.an('undefined');
60 | expect(mockEventEmitter.emit()).to.be.an('undefined');
61 | expect(mockEventEmitter.prependListener()).to.be.an('undefined');
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/test/lib/mockExpressResponse.spec.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai');
2 |
3 | const { expect } = chai;
4 | const sinonChai = require('sinon-chai');
5 |
6 | chai.use(sinonChai);
7 |
8 | const mockExpressResponse = require('../../lib/express/mock-express').response;
9 |
10 | describe('mockExpressResponse', () => {
11 | it('should expose .createResponse()', () => {
12 | expect(mockExpressResponse.createResponse).to.be.a('function');
13 | });
14 |
15 | describe('.createResponse()', () => {
16 | let response;
17 |
18 | before(() => {
19 | response = mockExpressResponse.createResponse();
20 | });
21 |
22 | it('should return an object', () => {
23 | expect(response).to.be.an('object');
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/test/lib/mockRequest.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import * as url from 'url';
3 | import * as querystring from 'querystring';
4 | import parseRange from 'range-parser';
5 | import { EventEmitter } from 'events';
6 | import { IncomingMessage } from 'http';
7 |
8 | import * as mockRequest from '../../lib/http-mock';
9 |
10 | describe('mockRequest', () => {
11 | it('should expose .createRequest()', () => {
12 | expect(mockRequest.createRequest).to.be.a('function');
13 | });
14 |
15 | describe('.createRequest()', () => {
16 | describe('without options', () => {
17 | it('should have event emitter prototype functions', () => {
18 | const request = mockRequest.createRequest();
19 | expect(request.on).to.be.a('function');
20 | expect(request.once).to.be.a('function');
21 | expect(request.emit).to.be.a('function');
22 | });
23 |
24 | it('should return an object', () => {
25 | const request = mockRequest.createRequest();
26 | expect(request).to.be.an('object');
27 | });
28 |
29 | it('should be an EventEmitter', () => {
30 | const request = mockRequest.createRequest();
31 | expect(request).to.be.an.instanceof(EventEmitter);
32 | });
33 |
34 | it('should expose Express Request object methods', () => {
35 | const request = mockRequest.createRequest();
36 | expect(request).to.have.property('get');
37 | expect(request.get).to.be.a('function');
38 |
39 | expect(request).to.have.property('header');
40 | expect(request.header).to.be.a('function');
41 |
42 | expect(request).to.have.property('param');
43 | expect(request.param).to.be.a('function');
44 | });
45 |
46 | it('should initialize with default options', () => {
47 | const request = mockRequest.createRequest();
48 | expect(request.method).to.equal('GET');
49 | expect(request.url).to.equal('');
50 | expect(request.originalUrl).to.equal(request.url);
51 | expect(request.baseUrl).to.equal(request.url);
52 | expect(request.path).to.equal('');
53 | expect(request.params).to.deep.equal({});
54 | expect(request.session).to.be.a('undefined');
55 | expect(request.cookies).to.deep.equal({});
56 | expect(request.signedCookies).to.be.a('undefined');
57 | expect(request.headers).to.be.an('object');
58 | expect(Object.keys(request.headers).length).to.equal(0);
59 | expect(request.body).to.deep.equal({});
60 | expect(request.query).to.deep.equal({});
61 | expect(request.files).to.deep.equal({});
62 | expect(request.ip).to.equal('127.0.0.1');
63 | expect(request.ips).to.deep.equal([request.ip]);
64 | });
65 | });
66 |
67 | describe('with options', () => {
68 | it('should set .method to options.method', () => {
69 | const options: mockRequest.RequestOptions = {
70 | method: 'POST'
71 | };
72 |
73 | const request = mockRequest.createRequest(options);
74 | expect(request.method).to.equal(options.method);
75 | });
76 |
77 | it('should set .url to options.url', () => {
78 | const options = {
79 | url: '/this/is/a/url'
80 | };
81 |
82 | const request = mockRequest.createRequest(options);
83 | expect(request.url).to.equal(options.url);
84 | expect(request.originalUrl).to.equal(options.url);
85 | expect(request.baseUrl).to.equal(options.url);
86 | });
87 |
88 | it('should set .url automatically', () => {
89 | const options = {
90 | path: '/this/is/a/path'
91 | };
92 |
93 | const expectedUrl = '/this/is/a/path';
94 |
95 | const request = mockRequest.createRequest(options);
96 | expect(request.url).to.equal(expectedUrl);
97 | });
98 |
99 | it('should set .baseUrl to options.baseUrl', () => {
100 | const options = {
101 | baseUrl: '/this'
102 | };
103 |
104 | const request = mockRequest.createRequest(options);
105 | expect(request.baseUrl).to.equal(options.baseUrl);
106 | });
107 |
108 | it('should set .originalUrl to options.originalUrl', () => {
109 | const options = {
110 | originalUrl: '/this/is/a/url'
111 | };
112 |
113 | const request = mockRequest.createRequest(options);
114 | expect(request.originalUrl).to.equal(options.originalUrl);
115 | });
116 |
117 | it('should set .path to options.path', () => {
118 | const options = {
119 | path: '/this/is/a/path'
120 | };
121 |
122 | const request = mockRequest.createRequest(options);
123 | expect(request.path).to.equal(options.path);
124 | });
125 |
126 | it('should set .path to pathname of options.url', () => {
127 | const options = {
128 | url: '/this/is/a/url'
129 | };
130 |
131 | const request = mockRequest.createRequest(options);
132 | expect(request.path).to.equal(url.parse(options.url).pathname);
133 | });
134 |
135 | it('should set .params to options.params', () => {
136 | const options = {
137 | params: {
138 | key1: 'value1',
139 | key2: 'value2'
140 | }
141 | };
142 |
143 | const request = mockRequest.createRequest(options);
144 | expect(request.params).to.deep.equal(options.params);
145 | });
146 |
147 | it('should set .session to options.session', () => {
148 | const options = {
149 | session: {
150 | key1: 'value1',
151 | key2: 'value2'
152 | }
153 | };
154 |
155 | const request = mockRequest.createRequest(options);
156 | expect(request.session).to.deep.equal(options.session);
157 | });
158 |
159 | it('should set .cookies to options.cookies', () => {
160 | const options = {
161 | cookies: {
162 | key1: 'value1',
163 | key2: 'value2'
164 | }
165 | };
166 |
167 | const request = mockRequest.createRequest(options);
168 | expect(request.cookies).to.deep.equal(options.cookies);
169 | });
170 |
171 | it('should set .signedCookies to options.signedCookies', () => {
172 | const options = {
173 | signedCookies: {
174 | key1: 'value1',
175 | key2: 'value2'
176 | }
177 | };
178 |
179 | const request = mockRequest.createRequest(options);
180 | expect(request.signedCookies).to.deep.equal(options.signedCookies);
181 | });
182 |
183 | it('should set .headers to options.headers', () => {
184 | const options = {
185 | headers: {
186 | key1: 'value1',
187 | key2: 'value2'
188 | }
189 | };
190 |
191 | const request = mockRequest.createRequest(options);
192 | expect(request.headers).to.deep.equal(options.headers);
193 | });
194 |
195 | it('should set .headers to options.headers and be accessible via get() and header() case-insensitively', () => {
196 | const options = {
197 | headers: {
198 | KEY1: 'value1',
199 | Key2: 'value2'
200 | }
201 | };
202 |
203 | const request = mockRequest.createRequest(options);
204 | expect(request.header('KEY1')).to.equal('value1');
205 | expect(request.get('KEY1')).to.equal('value1');
206 | expect(request.headers.get('KEY1')).to.equal('value1');
207 | expect(request.getHeader('KEY1')).to.equal('value1');
208 |
209 | expect(request.header('KEY2')).to.equal('value2');
210 | expect(request.get('KEY2')).to.equal('value2');
211 | expect(request.headers.get('KEY2')).to.equal('value2');
212 | expect(request.getHeader('KEY2')).to.equal('value2');
213 | });
214 |
215 | it('should set .headers directly and be accessible via get() and header() case-insensitively', () => {
216 | const request = mockRequest.createRequest();
217 | request.headers.KEY1 = 'value1';
218 |
219 | expect(request.header('KEY1')).to.equal('value1');
220 | expect(request.get('KEY1')).to.equal('value1');
221 | expect(request.headers.get('KEY1')).to.equal('value1');
222 | expect(request.getHeader('KEY1')).to.equal('value1');
223 | expect(request.headers.KEY1).to.equal('value1');
224 | });
225 |
226 | it('should set .body to options.body', () => {
227 | const options = {
228 | body: {
229 | key1: 'value1',
230 | key2: 'value2'
231 | }
232 | };
233 |
234 | const request = mockRequest.createRequest(options);
235 | expect(request.body).to.deep.equal(options.body);
236 | });
237 |
238 | it('should set .query to options.query', () => {
239 | const options = {
240 | query: {
241 | key1: 'value1',
242 | key2: 'value2'
243 | }
244 | };
245 |
246 | const request = mockRequest.createRequest(options);
247 | expect(request.query).to.deep.equal(options.query);
248 | });
249 |
250 | it('should set .files to options.files', () => {
251 | const options = {
252 | files: {
253 | key1: 'value1',
254 | key2: 'value2'
255 | }
256 | };
257 |
258 | const request = mockRequest.createRequest(options);
259 | expect(request.files).to.deep.equal(options.files);
260 | });
261 |
262 | it('should set .query to url query params when options.query not set', () => {
263 | const options = {
264 | url: '/path/to/url?key1=value1&key2=value2'
265 | };
266 | const parsedOptions = querystring.parse(options.url.split('?')[1]);
267 |
268 | const request = mockRequest.createRequest(options);
269 | expect(request.query).to.deep.equal(parsedOptions);
270 | });
271 |
272 | it('should accept and set non-standard options passed to it', () => {
273 | const options = {
274 | mySampleProp: 'la LA LA'
275 | };
276 |
277 | const request = mockRequest.createRequest(options);
278 | expect(request.mySampleProp).to.equal('la LA LA');
279 | });
280 |
281 | it('should set .ip to options.ip', () => {
282 | const options = {
283 | ip: '192.168.1.1'
284 | };
285 |
286 | const request = mockRequest.createRequest(options);
287 | expect(request.ip).to.equal(options.ip);
288 | });
289 |
290 | it('should set .ips to [options.ip]', () => {
291 | const options = {
292 | ip: '192.168.1.1'
293 | };
294 |
295 | const request = mockRequest.createRequest(options);
296 | expect(request.ips).to.deep.equal([options.ip]);
297 | });
298 | });
299 |
300 | it('should be able to create a Fetch API Request object', () => {
301 | const request = mockRequest.createRequest();
302 | expect(request.bodyUsed).to.be.undefined;
303 | });
304 | });
305 |
306 | describe('.get()/.header()', () => {
307 | it('should return header, when set in constructor', () => {
308 | const options = {
309 | headers: {
310 | 'Content-type': 'value'
311 | }
312 | };
313 |
314 | const request = mockRequest.createRequest(options);
315 | expect(request.get('Content-type')).to.equal('value');
316 | expect(request.header('Content-type')).to.equal('value');
317 | expect(request.headers.get('Content-type')).to.equal('value');
318 | expect(request.getHeader('Content-type')).to.equal('value');
319 | expect(request.headers['Content-type']).to.equal('value');
320 | });
321 |
322 | it('should return header, when set explicitly', () => {
323 | const request = mockRequest.createRequest();
324 |
325 | request.headers['Content-type'] = 'value';
326 |
327 | expect(request.get('Content-type')).to.equal('value');
328 | expect(request.header('Content-type')).to.equal('value');
329 | expect(request.headers.get('Content-type')).to.equal('value');
330 | expect(request.getHeader('Content-type')).to.equal('value');
331 | expect(request.headers['Content-type']).to.equal('value');
332 | });
333 |
334 | it('should return header, when set as an object (deprecated)', () => {
335 | const request = mockRequest.createRequest();
336 |
337 | // setting headers as an object is officially supported by Express
338 | // @ts-expect-error
339 | request.headers = { 'Content-type': 'value' };
340 |
341 | expect(request.get('Content-type')).to.equal('value');
342 | expect(request.header('Content-type')).to.equal('value');
343 | expect(request.getHeader('Content-type')).to.equal('value');
344 | expect(request.headers['Content-type']).to.equal('value');
345 |
346 | // request.headers.get() is not working in this case
347 | });
348 |
349 | it('should return referer, when request as referrer', () => {
350 | const options = {
351 | headers: {
352 | referer: 'value'
353 | }
354 | };
355 |
356 | const request = mockRequest.createRequest(options);
357 | expect(request.get('referrer')).to.equal('value');
358 | expect(request.header('referrer')).to.equal('value');
359 | expect(request.headers.get('referrer')).to.equal('value');
360 | expect(request.getHeader('referrer')).to.equal('value');
361 |
362 | // direct access keeps the original name
363 | expect(request.headers.referer).to.equal('value');
364 | });
365 |
366 | it('should return referrer, when request as referer', () => {
367 | const options = {
368 | headers: {
369 | referrer: 'value'
370 | }
371 | };
372 |
373 | const request = mockRequest.createRequest(options);
374 | expect(request.get('referer')).to.equal('value');
375 | expect(request.header('referer')).to.equal('value');
376 | expect(request.headers.get('referer')).to.equal('value');
377 | expect(request.getHeader('referer')).to.equal('value');
378 |
379 | // direct access keeps the original name
380 | expect(request.headers.referrer).to.equal('value');
381 | });
382 |
383 | it('should not return header, when not set', () => {
384 | const request = mockRequest.createRequest();
385 | expect(request.get('key')).to.be.a('undefined');
386 | expect(request.header('key')).to.be.a('undefined');
387 | expect(request.headers.get('key')).to.be.a('undefined');
388 | expect(request.getHeader('key')).to.be.a('undefined');
389 | expect(request.headers.key).to.be.a('undefined');
390 | });
391 | });
392 |
393 | describe('.is()', () => {
394 | it('should return type, when found in content-type header', () => {
395 | const options = {
396 | headers: {
397 | 'content-type': 'text/html',
398 | 'transfer-encoding': 'chunked'
399 | }
400 | };
401 |
402 | const request = mockRequest.createRequest(options);
403 | expect(request.is('html')).to.equal('html');
404 | });
405 |
406 | it('should return first matching type, given array of types', () => {
407 | const options = {
408 | headers: {
409 | 'content-type': 'text/html',
410 | 'transfer-encoding': 'chunked'
411 | }
412 | };
413 |
414 | const request = mockRequest.createRequest(options);
415 | expect(request.is(['json', 'html', 'text'])).to.equal('html');
416 | });
417 |
418 | it('should return false when type not found', () => {
419 | const options = {
420 | headers: {
421 | 'content-type': 'text/html',
422 | 'transfer-encoding': 'chunked'
423 | }
424 | };
425 |
426 | const request = mockRequest.createRequest(options);
427 | expect(request.is(['json'])).to.equal(false);
428 | });
429 | });
430 |
431 | describe('.accepts()', () => {
432 | it('returns type if the same as Accept header', () => {
433 | const request = mockRequest.createRequest({ headers: { accept: 'text/html' } });
434 | expect(request.accepts('text/html')).to.equal('text/html');
435 | expect(request.accepts('html')).to.equal('html');
436 | });
437 |
438 | it('returns the first matching type of an array of types', () => {
439 | const request = mockRequest.createRequest({ headers: { accept: 'text/html' } });
440 | expect(request.accepts(['json', 'html'])).to.equal('html');
441 | });
442 |
443 | it('returns false when types dont match', () => {
444 | const request = mockRequest.createRequest({ headers: { accept: 'text/html' } });
445 | expect(request.accepts(['json', 'xml'])).to.equal(false);
446 | });
447 | });
448 |
449 | describe('.acceptsEncodings()', () => {
450 | it('returns type if the same as Accept-Encoding header', () => {
451 | const request = mockRequest.createRequest({ headers: { 'Accept-Encoding': 'gzip' } });
452 | expect(request.acceptsEncodings('gzip')).to.equal('gzip');
453 | });
454 |
455 | it('returns the first matching type of an array of types', () => {
456 | const request = mockRequest.createRequest({ headers: { 'Accept-Encoding': 'gzip' } });
457 | expect(request.acceptsEncodings(['compress', 'gzip'])).to.equal('gzip');
458 | });
459 |
460 | it('returns false when types dont match', () => {
461 | const request = mockRequest.createRequest({ headers: { 'Accept-Encoding': 'gzip' } });
462 | expect(request.acceptsEncodings(['compress', 'deflate'])).to.equal(false);
463 | });
464 | });
465 |
466 | describe('.acceptsCharsets()', () => {
467 | let request: mockRequest.MockRequest;
468 |
469 | beforeEach(() => {
470 | request = mockRequest.createRequest({ headers: { 'Accept-Charset': 'utf-8' } });
471 | });
472 |
473 | it('returns type if the same as Accept-Charset header', () => {
474 | expect(request.acceptsCharsets('utf-8')).to.equal('utf-8');
475 | });
476 |
477 | it('returns the first matching type of an array of types', () => {
478 | expect(request.acceptsCharsets(['iso-8859-15', 'utf-8'])).to.equal('utf-8');
479 | });
480 |
481 | it('returns false when types dont match', () => {
482 | expect(request.acceptsCharsets(['iso-8859-15', 'us-ascii'])).to.equal(false);
483 | });
484 | });
485 |
486 | describe('.acceptsLanguages()', () => {
487 | it('returns type if the same as Accept-Language header', () => {
488 | const request = mockRequest.createRequest({ headers: { 'Accept-Language': 'en-GB' } });
489 | expect(request.acceptsLanguages('en-GB')).to.equal('en-GB');
490 | });
491 |
492 | it('returns the first matching type of an array of types', () => {
493 | const request = mockRequest.createRequest({ headers: { 'Accept-Language': 'en-GB' } });
494 | expect(request.acceptsLanguages(['de-DE', 'en-GB'])).to.equal('en-GB');
495 | });
496 |
497 | it('returns false when types dont match', () => {
498 | const request = mockRequest.createRequest({ headers: { 'Accept-Language': 'en-GB' } });
499 | expect(request.acceptsLanguages(['de-DE', 'fr-FR'])).to.equal(false);
500 | });
501 | });
502 |
503 | describe('.range()', () => {
504 | it('returns undefined if there is no Range header', () => {
505 | const request = mockRequest.createRequest();
506 | expect(request.range(10)).to.be.an('undefined');
507 | });
508 |
509 | const tests = [
510 | {
511 | // Unsatisfiable
512 | header: 'bytes=90-100',
513 | size: 10
514 | },
515 | {
516 | // Malformed
517 | header: 'by90-100',
518 | size: 10
519 | },
520 | {
521 | // Basic range
522 | header: 'bytes=50-55',
523 | size: 100
524 | },
525 | {
526 | // Complex, without options
527 | header: 'bytes=50-55,0-10,5-10,56-60',
528 | size: 100
529 | },
530 | {
531 | // With options
532 | header: 'bytes=50-55,0-10,5-10,56-60',
533 | size: 100,
534 | options: {
535 | combine: true
536 | }
537 | }
538 | ];
539 |
540 | tests.forEach((t) => {
541 | it(`returns the result of range-parser if there is a Range header of ${t.header} using size: ${t.size}`, () => {
542 | const request = mockRequest.createRequest({ headers: { range: t.header } });
543 | expect(request.range(t.size, t.options)).to.deep.equal(parseRange(t.size, t.header, t.options));
544 | });
545 | });
546 | });
547 |
548 | describe('.param()', () => {
549 | it('should return param, when found in params', () => {
550 | const options = {
551 | params: {
552 | key: 'value'
553 | }
554 | };
555 |
556 | const request = mockRequest.createRequest(options);
557 | expect(request.param('key')).to.equal('value');
558 | });
559 |
560 | it('should return falsy param, when found in params', () => {
561 | const options = {
562 | params: {
563 | key: 0
564 | }
565 | };
566 |
567 | const request = mockRequest.createRequest(options);
568 | expect(request.param('key')).to.equal(0);
569 | });
570 |
571 | it('should return param, when found in body', () => {
572 | const options = {
573 | body: {
574 | key: 'value'
575 | }
576 | };
577 |
578 | const request = mockRequest.createRequest(options);
579 | expect(request.param('key')).to.equal('value');
580 | });
581 |
582 | it('should return falsy param, when found in body', () => {
583 | const options = {
584 | body: {
585 | key: 0
586 | }
587 | };
588 |
589 | const request = mockRequest.createRequest(options);
590 | expect(request.param('key')).to.equal(0);
591 | });
592 |
593 | it('should return param, when found in query', () => {
594 | const options = {
595 | query: {
596 | key: 'value'
597 | }
598 | };
599 |
600 | const request = mockRequest.createRequest(options);
601 | expect(request.param('key')).to.equal('value');
602 | });
603 |
604 | it('should return falsy param, when found in query', () => {
605 | const options = {
606 | query: {
607 | key: 0
608 | }
609 | };
610 |
611 | const request = mockRequest.createRequest(options);
612 | expect(request.param('key')).to.equal(0);
613 | });
614 |
615 | it('should not return param, when not found in params/body/query', () => {
616 | const request = mockRequest.createRequest();
617 | expect(request.get('key')).to.be.a('undefined');
618 | expect(request.header('key')).to.be.a('undefined');
619 | expect(request.headers.get('key')).to.be.a('undefined');
620 | expect(request.headers.key).to.be.a('undefined');
621 | });
622 |
623 | it('should return defaultValue, when not found in params/body/query', () => {
624 | const request = mockRequest.createRequest();
625 | expect(request.get('key')).to.be.a('undefined');
626 | expect(request.param('key', 'defaultValue')).to.equal('defaultValue');
627 | });
628 |
629 | it('should return undefined, when not found in params/body/query', () => {
630 | const request = mockRequest.createRequest();
631 | expect(request.get('key')).to.be.a('undefined');
632 | expect(request.param('key')).to.be.an('undefined');
633 | });
634 | });
635 |
636 | describe('helper functions', () => {
637 | describe('._setParameter()', () => {
638 | it('should set param, when called with key and value', () => {
639 | const request = mockRequest.createRequest();
640 | request._setParameter('key', 'value');
641 | expect(request.param('key')).to.equal('value');
642 | });
643 |
644 | it('should unset param, when called with key and no value', () => {
645 | const request = mockRequest.createRequest();
646 | request._setParameter('key', 'value');
647 | request._setParameter('key');
648 | expect(request.param('key')).to.be.a('undefined');
649 | });
650 | });
651 |
652 | describe('._setSessionVariable()', () => {
653 | it('should set session constiable, when called with key and value', () => {
654 | const request = mockRequest.createRequest();
655 | request._setSessionVariable('key', 'value');
656 | expect(request.session.key).to.equal('value');
657 | });
658 |
659 | it('should unset session constiable, when called with key and no value', () => {
660 | const request = mockRequest.createRequest();
661 | request._setSessionVariable('key', 'value');
662 | request._setSessionVariable('key');
663 | expect(request.param('key')).to.be.a('undefined');
664 | });
665 | });
666 |
667 | describe('._setCookiesVariable()', () => {
668 | it('should set cookie, when called with key and value', () => {
669 | const request = mockRequest.createRequest();
670 | request._setCookiesVariable('key', 'value');
671 | expect(request.cookies.key).to.equal('value');
672 | });
673 |
674 | it('should unset cookie, when called with key and no value', () => {
675 | const request = mockRequest.createRequest();
676 | request._setCookiesVariable('key', 'value');
677 | request._setCookiesVariable('key');
678 | expect(request.cookies.key).to.be.a('undefined');
679 | });
680 | });
681 |
682 | describe('._setSignedCookiesVariable()', () => {
683 | it('should set signed cookie, when called with key and value', () => {
684 | const request = mockRequest.createRequest();
685 | request._setSignedCookiesVariable('key', 'value');
686 | expect(request.signedCookies.key).to.equal('value');
687 | });
688 |
689 | it('should unset signed cookie, when called with key and no value', () => {
690 | const request = mockRequest.createRequest();
691 | request._setSignedCookiesVariable('key', 'value');
692 | request._setSignedCookiesVariable('key');
693 | expect(request.signedCookies.key).to.be.a('undefined');
694 | });
695 | });
696 |
697 | describe('._setHeadersVariable()', () => {
698 | it('should set header, when called with key and value', () => {
699 | const request = mockRequest.createRequest();
700 | request._setHeadersVariable('Key', 'value');
701 |
702 | expect(request.get('Key')).to.equal('value');
703 | expect(request.header('Key')).to.equal('value');
704 | expect(request.headers.get('Key')).to.equal('value');
705 | expect(request.getHeader('Key')).to.equal('value');
706 | expect(request.headers.Key).to.equal('value');
707 | });
708 | });
709 |
710 | describe('._setFilesVariable()', () => {
711 | it('should set file, when called with key and value', () => {
712 | const request = mockRequest.createRequest();
713 | request._setFilesVariable('key', 'value');
714 | expect(request.files.key).to.equal('value');
715 | });
716 |
717 | it('should unset file, when called with key and no value', () => {
718 | const request = mockRequest.createRequest();
719 | request._setFilesVariable('key', 'value');
720 | request._setFilesVariable('key');
721 | expect(request.files.key).to.be.a('undefined');
722 | });
723 | });
724 |
725 | describe('._setMethod()', () => {
726 | it('should set method, when called with value', () => {
727 | const request = mockRequest.createRequest();
728 | const value = 'HEAD';
729 | request._setMethod(value);
730 | expect(request.method).to.equal(value);
731 | });
732 |
733 | it('should unset method, when called with no arguments', () => {
734 | const request = mockRequest.createRequest();
735 | request._setMethod();
736 | expect(request.method).to.be.a('undefined');
737 | });
738 | });
739 |
740 | describe('._setURL()', () => {
741 | it('should set url, when called with value', () => {
742 | const request = mockRequest.createRequest();
743 | const value = '/path/to/url';
744 | request._setURL(value);
745 | expect(request.url).to.equal(value);
746 | });
747 |
748 | it('should unset url, when called with no arguments', () => {
749 | const request = mockRequest.createRequest();
750 | request._setURL();
751 | expect(request.url).to.be.a('undefined');
752 | });
753 | });
754 |
755 | describe('._setBaseUrl()', () => {
756 | it('should set baseUrl, when called with value', () => {
757 | const value = '/path';
758 | const request = mockRequest.createRequest();
759 | request._setBaseUrl(value);
760 | expect(request.baseUrl).to.equal(value);
761 | });
762 |
763 | it('should unset baseUrl, when called with no arguments', () => {
764 | const request = mockRequest.createRequest();
765 | request._setBaseUrl();
766 | expect(request.baseUrl).to.be.a('undefined');
767 | });
768 | });
769 |
770 | describe('._setOriginalUrl()', () => {
771 | it('should set originalUrl, when called with value', () => {
772 | const value = '/path/to/url';
773 | const request = mockRequest.createRequest();
774 | request._setOriginalUrl(value);
775 | expect(request.originalUrl).to.equal(value);
776 | });
777 |
778 | it('should unset originalUrl, when called with no arguments', () => {
779 | const request = mockRequest.createRequest();
780 | request._setOriginalUrl();
781 | expect(request.originalUrl).to.be.a('undefined');
782 | });
783 | });
784 |
785 | describe('._setBody()', () => {
786 | it('should set body, when called with value', () => {
787 | const value = {
788 | key1: 'value1',
789 | key2: 'value2'
790 | };
791 | const request = mockRequest.createRequest();
792 | request._setBody(value);
793 | expect(request.body).to.deep.equal(value);
794 | });
795 |
796 | it('should unset body, when called with no arguments', () => {
797 | const request = mockRequest.createRequest();
798 | request._setBody();
799 | expect(request.body).to.be.a('undefined');
800 | });
801 | });
802 |
803 | describe('._addBody()', () => {
804 | it('should add body constiable, when called with key and value', () => {
805 | const request = mockRequest.createRequest();
806 | request._addBody('key', 'value');
807 | expect(request.body.key).to.equal('value');
808 | });
809 |
810 | it('should unset body constiable, when called with key and no value', () => {
811 | const request = mockRequest.createRequest();
812 | request._addBody('key', 'value');
813 | request._addBody('key');
814 | expect(request.body.key).to.be.a('undefined');
815 | });
816 | });
817 |
818 | describe('.send()', () => {
819 | it('should trigger data and end event when string is given', (done) => {
820 | const data = [] as string[];
821 | const request = mockRequest.createRequest();
822 | request.on('data', (chunk: any) => {
823 | data.push(chunk);
824 | });
825 | request.on('end', () => {
826 | const result = data.join('');
827 | expect(result).to.equal('test data');
828 | done();
829 | });
830 | request.send('test data');
831 | });
832 |
833 | it('should trigger data and end event when object is given', (done) => {
834 | const data = [] as string[];
835 | const dataTosend = { key: 'value' };
836 | const request = mockRequest.createRequest();
837 | request.on('data', (chunk: any) => {
838 | data.push(chunk);
839 | });
840 | request.on('end', () => {
841 | const result = data.join('');
842 | expect(JSON.parse(result)).to.deep.equal(dataTosend);
843 | done();
844 | });
845 | request.send(dataTosend);
846 | });
847 |
848 | it('should trigger data and end event when number is given', (done) => {
849 | const data = [] as string[];
850 | const request = mockRequest.createRequest();
851 | request.on('data', (chunk: any) => {
852 | data.push(chunk);
853 | });
854 | request.on('end', () => {
855 | const result = data.join('');
856 | expect(result).to.equal('35');
857 | done();
858 | });
859 | request.send(35);
860 | });
861 |
862 | it('should trigger data and end event when buffer is given', (done) => {
863 | const data = [] as string[];
864 | const bufferdata = Buffer.from('test data');
865 | const request = mockRequest.createRequest();
866 | request.on('data', (chunk: any) => {
867 | data.push(chunk);
868 | });
869 | request.on('end', () => {
870 | const result = data.join('');
871 | expect(result).to.equal('test data');
872 | done();
873 | });
874 | request.send(bufferdata);
875 | });
876 |
877 | it('should trigger data and end event when nothing is given', (done) => {
878 | const data = [] as string[];
879 | const request = mockRequest.createRequest();
880 | request.on('data', (chunk: any) => {
881 | data.push(chunk);
882 | });
883 | request.on('end', () => {
884 | const result = data.join('');
885 | expect(result).to.equal('');
886 | done();
887 | });
888 | request.send();
889 | });
890 | });
891 |
892 | describe('.hostname', () => {
893 | it("should return the host's main domain", () => {
894 | const options = {
895 | headers: {
896 | host: 'tobi.ferrets.example.com:5000'
897 | }
898 | };
899 | let request: mockRequest.MockRequest;
900 | request = mockRequest.createRequest(options);
901 | expect(request.hostname).to.equal('tobi.ferrets.example.com');
902 |
903 | options.headers.host = 'tobi.ferrets.example.com';
904 | request = mockRequest.createRequest(options);
905 | expect(request.hostname).to.equal('tobi.ferrets.example.com');
906 |
907 | options.headers.host = 'example.com';
908 | request = mockRequest.createRequest(options);
909 | expect(request.hostname).to.equal('example.com');
910 |
911 | options.headers.host = 'example.com:8443';
912 | request = mockRequest.createRequest(options);
913 | expect(request.hostname).to.equal('example.com');
914 |
915 | options.headers.host = 'localhost:3000';
916 | request = mockRequest.createRequest(options);
917 | expect(request.hostname).to.equal('localhost');
918 | });
919 |
920 | it('should return an empty string', () => {
921 | const options = {
922 | headers: {
923 | key1: 'key1'
924 | }
925 | };
926 | const request = mockRequest.createRequest(options);
927 | expect(request.hostname).to.equal('');
928 | });
929 |
930 | it('should return an predefined hostname', () => {
931 | const options = {
932 | hostname: 'predefined.host.com',
933 | headers: {
934 | host: 'other.host'
935 | }
936 | };
937 | const request = mockRequest.createRequest(options);
938 | expect(request.hostname).to.equal('predefined.host.com');
939 | });
940 | });
941 |
942 | describe('.subdomains', () => {
943 | it('should returns the host subdomains', () => {
944 | const options = {
945 | headers: {
946 | host: 'tobi.ferrets.example.com'
947 | }
948 | };
949 | const request = mockRequest.createRequest(options);
950 | expect(request.subdomains).to.be.an('array').that.includes('ferrets');
951 | expect(request.subdomains).to.be.an('array').that.includes('tobi');
952 | });
953 |
954 | it('should returns and empty array', () => {
955 | const options: mockRequest.RequestOptions = {
956 | headers: {
957 | key1: 'key1'
958 | }
959 | };
960 |
961 | let request: mockRequest.MockRequest;
962 | request = mockRequest.createRequest(options);
963 | expect(request.subdomains).to.be.an('array').to.have.lengthOf(0);
964 |
965 | options.headers!.host = 'example.com';
966 | request = mockRequest.createRequest(options);
967 | expect(request.subdomains).to.be.an('array').to.have.lengthOf(0);
968 | });
969 | });
970 | });
971 |
972 | describe('asyncIterator', () => {
973 | async function collect(asyncIterable: any) {
974 | const chunks = [] as string[];
975 | for await (const chunk of asyncIterable) {
976 | chunks.push(chunk);
977 | }
978 | return chunks;
979 | }
980 |
981 | it('should iterate when sending data', async () => {
982 | const request = mockRequest.createRequest();
983 |
984 | const chunksPromise = collect(request);
985 | request.send('test data');
986 |
987 | const data = (await chunksPromise).join('');
988 | expect(data).to.equal('test data');
989 | });
990 |
991 | it('should iterate synchronous pushes', async () => {
992 | const request = mockRequest.createRequest();
993 |
994 | const chunksPromise = collect(request);
995 | request.emit('data', Buffer.from('foo'));
996 | request.emit('data', Buffer.from('bar'));
997 | request.emit('data', Buffer.from('baz'));
998 | request.emit('end');
999 |
1000 | const data = (await chunksPromise).join('');
1001 | expect(data).to.equal('foobarbaz');
1002 | });
1003 |
1004 | it('should ignore push after end', async () => {
1005 | const request = mockRequest.createRequest();
1006 |
1007 | const chunksPromise = collect(request);
1008 | request.emit('data', Buffer.from('foo'));
1009 | request.emit('end');
1010 | request.emit('data', Buffer.from('bar'));
1011 |
1012 | const data = (await chunksPromise).join('');
1013 | expect(data).to.equal('foo');
1014 | });
1015 |
1016 | it('should iterate asynchronous pushes', async () => {
1017 | const request = mockRequest.createRequest();
1018 |
1019 | const chunksPromise = collect(request);
1020 | request.emit('data', Buffer.from('foo'));
1021 | await new Promise((r) => {
1022 | setTimeout(r);
1023 | });
1024 | request.emit('data', Buffer.from('bar'));
1025 | await new Promise((r) => {
1026 | setTimeout(r);
1027 | });
1028 | request.emit('data', Buffer.from('baz'));
1029 | await new Promise((r) => {
1030 | setTimeout(r);
1031 | });
1032 | request.emit('end');
1033 |
1034 | const data = (await chunksPromise).join('');
1035 | expect(data).to.equal('foobarbaz');
1036 | });
1037 |
1038 | it('should support asynchronous pushes while iterating', async () => {
1039 | const request = mockRequest.createRequest();
1040 |
1041 | const chunksPromise = (async () => {
1042 | const extraPushes = ['3', '2', '1'];
1043 | const chunks = [] as string[];
1044 | for await (const chunk of request) {
1045 | chunks.push(chunk);
1046 | if (extraPushes.length > 0) {
1047 | const toCreate = extraPushes.pop()!;
1048 | request.emit('data', Buffer.from(toCreate));
1049 | await new Promise((r) => {
1050 | setTimeout(r);
1051 | });
1052 | }
1053 | }
1054 | return chunks;
1055 | })();
1056 |
1057 | request.emit('data', Buffer.from('foo'));
1058 | await new Promise((r) => {
1059 | setTimeout(r);
1060 | });
1061 | request.emit('data', Buffer.from('bar'));
1062 | await new Promise((r) => {
1063 | setTimeout(r);
1064 | });
1065 | request.emit('data', Buffer.from('baz'));
1066 | await new Promise((r) => {
1067 | setTimeout(r);
1068 | });
1069 | request.emit('end');
1070 |
1071 | const data = (await chunksPromise).join('');
1072 | expect(data).to.equal('foo1bar2baz3');
1073 | });
1074 |
1075 | it('supports error', async () => {
1076 | const request = mockRequest.createRequest();
1077 |
1078 | /** @type {AsyncIterator} */
1079 | const iterator = request[Symbol.asyncIterator]();
1080 | const error = new Error('Test error');
1081 |
1082 | const nextPromise = iterator.next();
1083 | request.emit('error', error);
1084 |
1085 | try {
1086 | await nextPromise;
1087 | expect.fail();
1088 | } catch (e) {
1089 | expect(e).to.equal(error);
1090 | }
1091 | });
1092 |
1093 | it('supports throw', async () => {
1094 | const request = mockRequest.createRequest();
1095 |
1096 | /** @type {AsyncIterator} */
1097 | const iterator: AsyncIterator = request[Symbol.asyncIterator]();
1098 | const error = new Error('Test error');
1099 |
1100 | const nextPromise = iterator.next();
1101 | request.emit('data', Buffer.from('foo'));
1102 | await nextPromise;
1103 |
1104 | try {
1105 | await iterator.throw!(error);
1106 | expect.fail();
1107 | } catch (e) {
1108 | expect(e).to.equal(error);
1109 | }
1110 | });
1111 |
1112 | it('first error wins', async () => {
1113 | const request = mockRequest.createRequest();
1114 |
1115 | /** @type {AsyncIterator} */
1116 | const iterator: AsyncIterator = request[Symbol.asyncIterator]();
1117 | const error1 = new Error('Test error 1');
1118 | const error2 = new Error('Test error 2');
1119 |
1120 | const nextPromise = iterator.next();
1121 | request.emit('error', error1);
1122 | request.emit('error', error2);
1123 |
1124 | try {
1125 | await nextPromise;
1126 | expect.fail();
1127 | } catch (e) {
1128 | expect(e).to.equal(error1);
1129 | }
1130 | });
1131 |
1132 | it('supports return', async () => {
1133 | const request = mockRequest.createRequest();
1134 |
1135 | /** @type {AsyncIterator} */
1136 | const iterator: AsyncIterator = request[Symbol.asyncIterator]();
1137 |
1138 | const result = await iterator.return!();
1139 | expect(result.done).to.equal(true);
1140 | });
1141 |
1142 | ['close', 'error'].forEach((event) => {
1143 | it(`discards buffer on ${event}`, async () => {
1144 | const request = mockRequest.createRequest();
1145 |
1146 | const chunksPromise = (async () => {
1147 | const chunks = [] as string[];
1148 | try {
1149 | for await (const data of request) {
1150 | chunks.push(data);
1151 | }
1152 | } catch (e) {
1153 | // Ignore
1154 | }
1155 | return chunks;
1156 | })();
1157 |
1158 | request.emit('data', Buffer.from('foo'));
1159 | await new Promise((r) => {
1160 | setTimeout(r);
1161 | });
1162 | request.emit('data', Buffer.from('bar'));
1163 | request.emit(event, event === 'error' ? new Error('Test error') : undefined);
1164 | request.emit('data', Buffer.from('baz'));
1165 |
1166 | const data = (await chunksPromise).join('');
1167 | expect(data).to.equal('foo');
1168 | });
1169 | });
1170 |
1171 | it('emits custom event after creation', async () => {
1172 | const request = mockRequest.createRequest();
1173 |
1174 | request.on('async_iterator', () => {
1175 | request.emit('data', Buffer.from('foo'));
1176 | request.emit('data', Buffer.from('bar'));
1177 | request.emit('data', Buffer.from('baz'));
1178 | request.emit('end');
1179 | });
1180 |
1181 | const data = (await collect(request)).join('');
1182 | expect(data).to.equal('foobarbaz');
1183 | });
1184 |
1185 | if (typeof global.Request === 'function') {
1186 | it('can be fed to a Fetch API Request body', async () => {
1187 | // TODO: what is the purpose of this test?
1188 | const request = mockRequest.createRequest();
1189 |
1190 | const webRequest = new Request('http://example.com', {
1191 | method: 'POST',
1192 | headers: {
1193 | 'Content-Type': 'application/json'
1194 | },
1195 | // @ts-ignore:next-line
1196 | body: request,
1197 | // @ts-ignore:next-line
1198 | duplex: 'half'
1199 | });
1200 |
1201 | request.on('async_iterator', () => {
1202 | request.emit('data', Buffer.from('{ "foo": "b'));
1203 | request.emit('data', Buffer.from('ar" }'));
1204 | request.emit('end');
1205 | });
1206 |
1207 | const webRequestJson = await webRequest.json();
1208 | expect(webRequestJson).to.deep.equal({ foo: 'bar' });
1209 | });
1210 | }
1211 | });
1212 | });
1213 |
--------------------------------------------------------------------------------
/test/lib/mockWritableStream.spec.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai');
2 |
3 | const { expect } = chai;
4 |
5 | const MockWritableStream = require('../../lib/mockWritableStream');
6 |
7 | let mockWritableStream;
8 |
9 | describe('mockWritableStream', () => {
10 | before(() => {
11 | mockWritableStream = new MockWritableStream();
12 | });
13 |
14 | it('should be a function', () => {
15 | expect(MockWritableStream).to.be.a('function');
16 | });
17 |
18 | it('should be an object factory', () => {
19 | expect(mockWritableStream).to.be.a('object');
20 | expect(mockWritableStream).to.be.an.instanceof(MockWritableStream);
21 | });
22 |
23 | it('should expose "MockWritableStream" prototype', () => {
24 | expect(mockWritableStream).to.have.property('end');
25 | expect(mockWritableStream.end).to.be.a('function');
26 |
27 | expect(mockWritableStream).to.have.property('destroy');
28 | expect(mockWritableStream.destroy).to.be.a('function');
29 |
30 | expect(mockWritableStream).to.have.property('destroySoon');
31 | expect(mockWritableStream.destroySoon).to.be.a('function');
32 | });
33 |
34 | it('should return undefined when methods called', () => {
35 | expect(mockWritableStream.end()).to.be.an('undefined');
36 | expect(mockWritableStream.destroy()).to.be.an('undefined');
37 | expect(mockWritableStream.destroySoon()).to.be.an('undefined');
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/test/lib/node-http-mock.spec.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai');
2 | const httpMock = require('../../lib/http-mock');
3 |
4 | const { expect } = chai;
5 |
6 | describe('http-mock', () => {
7 | it('should export .createRequest()', () => {
8 | expect(httpMock.createRequest).to.be.a('function');
9 | });
10 |
11 | it('should export .createResponse()', () => {
12 | expect(httpMock.createResponse).to.be.a('function');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "commonjs" /* Specify what module code is generated. */,
29 | // "rootDir": "./", /* Specify the root folder within your source files. */
30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
42 | // "resolveJsonModule": true, /* Enable importing .json files. */
43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
45 |
46 | /* JavaScript Support */
47 | "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */,
48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
50 |
51 | /* Emit */
52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
58 | // "outDir": "./", /* Specify an output folder for all emitted files. */
59 | // "removeComments": true, /* Disable emitting comments. */
60 | "noEmit": true /* Disable emitting files from a compilation. */,
61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
68 | // "newLine": "crlf", /* Set the newline character for emitting files. */
69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
75 |
76 | /* Interop Constraints */
77 | "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
80 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
82 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
83 |
84 | /* Type Checking */
85 | "strict": true /* Enable all strict type-checking options. */,
86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
93 | "alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
104 |
105 | /* Completeness */
106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
108 | },
109 | "include": ["lib/**/*"]
110 | }
111 |
--------------------------------------------------------------------------------