Skip to content

Commit

Permalink
feat(@jest/environment, jest-runtime): allow passing a generic type a…
Browse files Browse the repository at this point in the history
…rgument to `jest.createMockFromModule<T>()` method (#13202)
  • Loading branch information
mrazauskas committed Sep 3, 2022
1 parent 616fcf5 commit 5c22e4f
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 20 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

### Features

- `[@jest/environment, jest-runtime]` Allow passing a generic type argument to `jest.createMockFromModule<T>()` method ([#13202](https://github.com/facebook/jest/pull/13202))

### Fixes

### Chore & Maintenance
Expand Down
52 changes: 39 additions & 13 deletions docs/JestObjectAPI.md
Expand Up @@ -5,6 +5,16 @@ title: The Jest Object

The `jest` object is automatically in scope within every test file. The methods in the `jest` object help create mocks and let you control Jest's overall behavior. It can also be imported explicitly by via `import {jest} from '@jest/globals'`.

:::info

The TypeScript examples from this page will only work as documented if you import global APIs from `'@jest/globals'`:

```ts
import {expect, jest, test} from '@jest/globals';
```

:::

## Methods

import TOCInline from '@theme/TOCInline';
Expand Down Expand Up @@ -96,18 +106,12 @@ _Note: this method was previously called `autoMockOn`. When using `babel-jest`,

### `jest.createMockFromModule(moduleName)`

##### renamed in Jest **26.0.0+**

Also under the alias: `.genMockFromModule(moduleName)`

Given the name of a module, use the automatic mocking system to generate a mocked version of the module for you.

This is useful when you want to create a [manual mock](ManualMocks.md) that extends the automatic mock's behavior.
This is useful when you want to create a [manual mock](ManualMocks.md) that extends the automatic mock's behavior:

Example:

```js title="utils.js"
export default {
```js tab={"span":2} title="utils.js"
module.exports = {
authorize: () => {
return 'token';
},
Expand All @@ -116,12 +120,34 @@ export default {
```

```js title="__tests__/createMockFromModule.test.js"
const utils = jest.createMockFromModule('../utils').default;
const utils = jest.createMockFromModule('../utils');

utils.isAuthorized = jest.fn(secret => secret === 'not wizard');

test('implementation created by jest.createMockFromModule', () => {
expect(utils.authorize.mock).toBeTruthy();
expect(utils.isAuthorized('not wizard')).toEqual(true);
expect(jest.isMockFunction(utils.authorize)).toBe(true);
expect(utils.isAuthorized('not wizard')).toBe(true);
});
```

```ts tab={"span":2} title="utils.ts"
export const utils = {
authorize: () => {
return 'token';
},
isAuthorized: (secret: string) => secret === 'wizard',
};
```

```ts title="__tests__/createMockFromModule.test.ts"
const {utils} =
jest.createMockFromModule<typeof import('../utils')>('../utils');

utils.isAuthorized = jest.fn((secret: string) => secret === 'not wizard');

test('implementation created by jest.createMockFromModule', () => {
expect(jest.isMockFunction(utils.authorize)).toBe(true);
expect(utils.isAuthorized('not wizard')).toBe(true);
});
```

Expand Down Expand Up @@ -180,7 +206,7 @@ module.exports = {
```

```js title="__tests__/example.test.js"
const example = jest.createMockFromModule('./example');
const example = jest.createMockFromModule('../example');

test('should run example code', () => {
// creates a new mocked function with no formal arguments.
Expand Down
7 changes: 4 additions & 3 deletions packages/jest-environment/src/index.ts
Expand Up @@ -8,7 +8,7 @@
import type {Context} from 'vm';
import type {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
import type {Circus, Config, Global} from '@jest/types';
import type {ModuleMocker} from 'jest-mock';
import type {Mocked, ModuleMocker} from 'jest-mock';

export type EnvironmentContext = {
console: Console;
Expand Down Expand Up @@ -92,7 +92,7 @@ export interface Jest {
* This is useful when you want to create a manual mock that extends the
* automatic mock's behavior.
*/
createMockFromModule(moduleName: string): unknown;
createMockFromModule<T = unknown>(moduleName: string): Mocked<T>;
/**
* Indicates that the module system should never return a mocked version of
* the specified module and its dependencies.
Expand Down Expand Up @@ -129,6 +129,7 @@ export interface Jest {
* Creates a mock function. Optionally takes a mock implementation.
*/
fn: ModuleMocker['fn'];
// TODO remove `genMockFromModule()` in Jest 30
/**
* Given the name of a module, use the automatic mocking system to generate a
* mocked version of the module for you.
Expand All @@ -138,7 +139,7 @@ export interface Jest {
*
* @deprecated Use `jest.createMockFromModule()` instead
*/
genMockFromModule(moduleName: string): unknown;
genMockFromModule<T = unknown>(moduleName: string): Mocked<T>;
/**
* When mocking time, `Date.now()` will also be mocked. If you for some reason
* need access to the real current time, you can invoke this function.
Expand Down
8 changes: 4 additions & 4 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -50,7 +50,7 @@ import {
import type {Config, Global} from '@jest/types';
import HasteMap, {IModuleMap} from 'jest-haste-map';
import {formatStackTrace, separateMessageFromStack} from 'jest-message-util';
import type {MockFunctionMetadata, ModuleMocker} from 'jest-mock';
import type {MockMetadata, ModuleMocker} from 'jest-mock';
import {escapePathForRegex} from 'jest-regex-util';
import Resolver, {ResolveModuleConfig} from 'jest-resolve';
import {EXTENSION as SnapshotExtension} from 'jest-snapshot';
Expand Down Expand Up @@ -168,7 +168,7 @@ export default class Runtime {
private _isCurrentlyExecutingManualMock: string | null;
private _mainModule: Module | null;
private readonly _mockFactories: Map<string, () => unknown>;
private readonly _mockMetaDataCache: Map<string, MockFunctionMetadata>;
private readonly _mockMetaDataCache: Map<string, MockMetadata<any>>;
private _mockRegistry: Map<string, any>;
private _isolatedMockRegistry: Map<string, any> | null;
private _moduleMockRegistry: Map<string, VMModule>;
Expand Down Expand Up @@ -1710,7 +1710,7 @@ export default class Runtime {
return Module;
}

private _generateMock(from: string, moduleName: string) {
private _generateMock<T>(from: string, moduleName: string) {
const modulePath =
this._resolver.resolveStubModuleName(from, moduleName) ||
this._resolveCjsModule(from, moduleName);
Expand Down Expand Up @@ -1747,7 +1747,7 @@ export default class Runtime {
}
this._mockMetaDataCache.set(modulePath, mockMetadata);
}
return this._moduleMocker.generateFromMetadata(
return this._moduleMocker.generateFromMetadata<T>(
// added above if missing
this._mockMetaDataCache.get(modulePath)!,
);
Expand Down
8 changes: 8 additions & 0 deletions packages/jest-types/__typetests__/jest.test.ts
Expand Up @@ -62,7 +62,15 @@ expectError(jest.autoMockOff(true));
expectType<typeof jest>(jest.autoMockOn());
expectError(jest.autoMockOn(false));

const someModule = {
methodA: () => {},
propertyB: 'B',
};

expectType<unknown>(jest.createMockFromModule('moduleName'));
expectType<Mocked<typeof someModule>>(
jest.createMockFromModule<typeof someModule>('moduleName'),
);
expectError(jest.createMockFromModule());

expectType<typeof jest>(jest.deepUnmock('moduleName'));
Expand Down

0 comments on commit 5c22e4f

Please sign in to comment.