-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
feat: Allow mocking property value in tests #13496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
15697c7
9c270ca
20b881b
930036d
c739864
0f227c2
1c4535b
4a1aeb6
c79837d
cceffa0
6c17cc0
d828f11
4f9ac47
b3fb383
208df4d
2ed2ca8
ba36a3b
25d1b24
472841c
2f9c9c9
33c30b9
f0ffae1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -515,6 +515,20 @@ test('async test', async () => { | |||||
| }); | ||||||
| ``` | ||||||
|
|
||||||
| ## Replaced Properties | ||||||
|
|
||||||
| ### `replacedProperty.replaceValue(value)` | ||||||
michal-kocarek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| Changes the value of already replaced property. This is useful when you want to replace property and then adjust the value in specific tests. As an alternative, you can call [`jest.replaceProperty()`](JestObjectAPI.md#jestreplacepropertyobject-propertykey-value) multiple times on same property. | ||||||
|
|
||||||
| ### `replacedProperty.restore()` | ||||||
|
|
||||||
| Restores object's property to the original value. | ||||||
|
|
||||||
| Beware that `replacedProperty.restore()` only works when the property value was replaced with [`jest.replaceProperty()`](JestObjectAPI.md#jestreplacepropertyobject-propertykey-value). | ||||||
|
|
||||||
| The [`restoreMocks`](configuration#restoremocks-boolean) configuration option is available to restore replaced properties automatically before each test. | ||||||
|
|
||||||
| ## TypeScript Usage | ||||||
|
|
||||||
| <TypeScriptExamplesNote /> | ||||||
|
|
@@ -594,6 +608,39 @@ test('returns correct data', () => { | |||||
|
|
||||||
| Types of classes, functions or objects can be passed as type argument to `jest.Mocked<Source>`. If you prefer to constrain the input type, use: `jest.MockedClass<Source>`, `jest.MockedFunction<Source>` or `jest.MockedObject<Source>`. | ||||||
|
|
||||||
| ### `jest.Replaced<Source>` | ||||||
|
|
||||||
| The `jest.Replaced<Source>` utility type returns the `Source` type wrapped with type definitions of Jest [replaced property](#replaced-properties). | ||||||
|
|
||||||
| ```ts title="src/utils.ts" | ||||||
| export function isLocalhost(): boolean { | ||||||
| return process.env['HOSTNAME'] === 'localhost'; | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ```ts title="src/__tests__/utils.test.ts" | ||||||
| import {afterEach, expect, it, jest} from '@jest/globals'; | ||||||
| import {isLocalhost} from '../utils'; | ||||||
michal-kocarek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| let replacedEnv: jest.Replaced<typeof process.env> | undefined = undefined; | ||||||
|
|
||||||
| afterEach(() => { | ||||||
| replacedEnv?.restore(); | ||||||
| }); | ||||||
|
|
||||||
| it('isLocalhost should detect localhost environment', () => { | ||||||
| replacedEnv = jest.replaceProperty(process, 'env', {HOSTNAME: 'localhost'}); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why do we replace all of
Suggested change
instead?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, that seems better, although then
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This opens a design question. One cannot right now replace property that does not exist on an object. Assuming 'HOSTNAME' might or might not be in the env... What should we do in this case? The Sinon.js implementation forbids creation of new properties. It must exist on an object (or in the prototype chain). I applied same restriction here. What do you think, @SimenB , @mrazauskas and @eps1lon ? Should we allow adding undefined properties to the object (which is actually quite simple to do), or should be more restrictive (and possibly open this behavior in future). I do not have strong thoughts here...
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hm good point. I would expect that we can mock non-existing properties. But can see how
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could have a separate options bag (
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that works for our use case. And the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@eps1lon: I am a bit afraid of typos or invalid usages. People might be mocking stuff:
And if one thinks about it... How common/rare is the use case when we need to mock something that does not exist? I can think of a case when people might want to mock either a new key of "dictionary" or some new array element. In that case, they should mock the parent anyway I guess... In same manner like the process.env. E.g.: // Instead of
jest.replaceProperty(httpAgent.defaultHeaders, 'X-API-Key', mockKey);
// Use
jest.replaceProperty(httpAgent, 'defaultHeaders', {...httpAgent.defaultHeaders, 'X-API-Key': mockKey});
@SimenB This makes sense. How should I proceed?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can do that in a followup to allow mocking non-existing props. But if you wanna get started right away that'd be awesome 😀
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, this makes sense. I will implement it as separate PR, as this is already growing quite a lot. :) |
||||||
|
|
||||||
| expect(isLocalhost()).toBe(true); | ||||||
| }); | ||||||
|
|
||||||
| it('isLocalhost should detect non-localhost environment', () => { | ||||||
| replacedEnv = jest.replaceProperty(process, 'env', {HOSTNAME: 'example.com'}); | ||||||
|
|
||||||
| expect(isLocalhost()).toBe(false); | ||||||
| }); | ||||||
| ``` | ||||||
|
|
||||||
| ### `jest.mocked(source, options?)` | ||||||
|
|
||||||
| The `mocked()` helper method wraps types of the `source` object and its deep nested members with type definitions of Jest mock function. You can pass `{shallow: true}` as the `options` argument to disable the deeply mocked behavior. | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import {isLocalhost} from '../utils'; | ||
|
|
||
| afterEach(() => { | ||
| jest.restoreAllMocks(); | ||
| }); | ||
|
|
||
| it('isLocalhost should detect localhost environment', () => { | ||
| jest.replaceProperty(process, 'env', {HOSTNAME: 'localhost'}); | ||
|
|
||
| expect(isLocalhost()).toBe(true); | ||
| }); | ||
|
|
||
| it('isLocalhost should detect non-localhost environment', () => { | ||
| jest.replaceProperty(process, 'env', {HOSTNAME: 'example.com'}); | ||
|
|
||
| expect(isLocalhost()).toBe(false); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export function isLocalhost() { | ||
| return process.env.HOSTNAME === 'localhost'; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. | ||
|
|
||
| import {afterEach, beforeEach, expect, it, jest} from '@jest/globals'; | ||
| import {isLocalhost} from '../utils'; | ||
|
|
||
| let replacedEnv: jest.Replaced<typeof process.env> | undefined = undefined; | ||
|
|
||
| beforeEach(() => { | ||
| replacedEnv = jest.replaceProperty(process, 'env', {}); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| replacedEnv?.restore(); | ||
| }); | ||
|
|
||
| it('isLocalhost should detect localhost environment', () => { | ||
| replacedEnv.replaceValue({HOSTNAME: 'localhost'}); | ||
|
|
||
| expect(isLocalhost()).toBe(true); | ||
| }); | ||
|
|
||
| it('isLocalhost should detect non-localhost environment', () => { | ||
| expect(isLocalhost()).toBe(false); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. | ||
|
|
||
| export function isLocalhost() { | ||
| return process.env.HOSTNAME === 'localhost'; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.