Skip to content

Commit f976135

Browse files
committedSep 28, 2018
feat(helpers): adds a mocked test helper for mock typings
The `jest.mock()` does not change the type of the exported values from a module. So we have to: ```ts expect((foo as any).mock.calls[0][0]).toBe('foo') ``` Now with the `mocked` helper it is possible to do: ```ts expect(mocked(foo).mock.calls[0][0]).toBe('foo') ``` It is also possible to deeply mock a module right after importing it. Docs will be updated with related details. Closes #576
1 parent 13cb48d commit f976135

15 files changed

+136
-13
lines changed
 

Diff for: ‎e2e/__cases__/test-helpers/fail.spec.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { mocked } from 'ts-jest'
2+
import { foo, bar } from './to-mock'
3+
jest.mock('./to-mock')
4+
5+
it('should fail to compile', () => {
6+
// the method does not accept any arg
7+
expect(mocked(foo)('hello')).toBeUndefined()
8+
// the method accepts a string so typing should fail here
9+
expect(mocked(bar, true).dummy.deep.deeper(42)).toBeUndefined()
10+
})

Diff for: ‎e2e/__cases__/test-helpers/pass.spec.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { mocked } from 'ts-jest'
2+
import { foo, bar } from './to-mock'
3+
jest.mock('./to-mock')
4+
5+
test('foo', () => {
6+
// real returns 'foo', mocked returns 'bar'
7+
expect(foo()).toBeUndefined()
8+
expect(mocked(foo).mock.calls.length).toBe(1)
9+
})
10+
11+
test('bar', () => {
12+
const mockedBar = mocked(bar, true)
13+
// real returns 'foo', mocked returns 'bar'
14+
expect(mockedBar()).toBeUndefined()
15+
expect(mockedBar.dummy.deep.deeper()).toBeUndefined()
16+
expect(mockedBar.dummy.deep.deeper.mock.calls.length).toBe(1)
17+
})

Diff for: ‎e2e/__cases__/test-helpers/to-mock.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const foo = () => 'foo'
2+
3+
export function bar() {
4+
return 'bar'
5+
}
6+
export namespace bar {
7+
export function dummy() {
8+
return 'dummy'
9+
}
10+
export namespace dummy {
11+
export const deep = {
12+
deeper: (one: string = '1') => `deeper ${one}`
13+
}
14+
}
15+
}
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`test-helpers 1`] = `
4+
× jest --no-cache
5+
↳ exit code: 1
6+
===[ STDOUT ]===================================================================
7+
8+
===[ STDERR ]===================================================================
9+
PASS ./pass.spec.ts
10+
FAIL ./fail.spec.ts
11+
● Test suite failed to run
12+
13+
TypeScript diagnostics (customize using \`[jest-config].globals.ts-jest.diagnostics\` option):
14+
fail.spec.ts:7:10 - error TS2554: Expected 0 arguments, but got 1.
15+
16+
7 expect(mocked(foo)('hello')).toBeUndefined()
17+
~~~~~~~~~~~~~~~~~~~~
18+
fail.spec.ts:9:46 - error TS2345: Argument of type '42' is not assignable to parameter of type 'string'.
19+
20+
9 expect(mocked(bar, true).dummy.deep.deeper(42)).toBeUndefined()
21+
~~
22+
23+
Test Suites: 1 failed, 1 passed, 2 total
24+
Tests: 2 passed, 2 total
25+
Snapshots: 0 total
26+
Time: XXs
27+
Ran all test suites.
28+
================================================================================
29+
`;

Diff for: ‎e2e/__tests__/test-helpers.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { configureTestCase } from '../__helpers__/test-case'
2+
3+
test('test-helpers', () => {
4+
const test = configureTestCase('test-helpers', { noCache: true })
5+
expect(test.run(1)).toMatchSnapshot()
6+
})

Diff for: ‎src/__helpers__/mocks.ts

-8
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,6 @@ import { testing } from 'bs-logger'
22

33
import { rootLogger } from '../util/logger'
44

5-
// typings helper
6-
export function mocked<T>(val: T): T extends (...args: any[]) => any ? jest.MockInstance<T> : jest.Mocked<T> {
7-
return val as any
8-
}
9-
export function spied<T>(val: T): T extends (...args: any[]) => any ? jest.SpyInstance<T> : jest.Mocked<T> {
10-
return val as any
11-
}
12-
135
export const logTargetMock = () => (rootLogger as testing.LoggerMock).target
146

157
export const mockObject = <T, M>(obj: T, newProps: M): T & M & { mockRestore: () => T } => {

Diff for: ‎src/cli/cli.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as _fs from 'fs'
22
import { normalize, resolve } from 'path'
33

4-
import { logTargetMock, mockObject, mockWriteStream, mocked } from '../__helpers__/mocks'
4+
import { mocked } from '../..'
5+
import { logTargetMock, mockObject, mockWriteStream } from '../__helpers__/mocks'
56

67
import { processArgv } from '.'
78

Diff for: ‎src/config/config-set.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { resolve } from 'path'
33
import ts, { Diagnostic, DiagnosticCategory, ModuleKind, ScriptTarget } from 'typescript'
44

55
import * as _myModule from '..'
6+
import { mocked } from '../..'
67
import * as fakers from '../__helpers__/fakers'
7-
import { logTargetMock, mocked } from '../__helpers__/mocks'
8+
import { logTargetMock } from '../__helpers__/mocks'
89
import { TsJestGlobalOptions } from '../types'
910
import * as _backports from '../util/backports'
1011
import { normalizeSlashes } from '../util/normalize-slashes'

Diff for: ‎src/index.spec.ts

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ describe('ts-jest', () => {
2727
it('should export a `createJestPreset` function', () => {
2828
expect(typeof tsJest.createJestPreset).toBe('function')
2929
})
30+
it('should export a `mocked` function', () => {
31+
expect(typeof tsJest.mocked).toBe('function')
32+
})
3033
it('should export a `pathsToModuleNameMapper` function', () => {
3134
expect(typeof tsJest.pathsToModuleNameMapper).toBe('function')
3235
})

Diff for: ‎src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { TsJestTransformer } from './ts-jest-transformer'
77
import { TsJestGlobalOptions } from './types'
88
import { VersionCheckers } from './util/version-checkers'
99

10+
export * from './util/testing'
11+
1012
// tslint:disable-next-line:no-var-requires
1113
export const version: string = require('../package.json').version
1214
export const digest: string = readFileSync(resolve(__dirname, '..', '.ts-jest-digest'), 'utf8')

Diff for: ‎src/types.ts

+30
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,33 @@ export interface AstTransformerDesc {
180180
version: number
181181
factory(cs: ConfigSet): TransformerFactory<SourceFile>
182182
}
183+
184+
// test helpers
185+
186+
interface MockWithArgs<T> extends Function, jest.MockInstance<T> {
187+
new (...args: ArgumentsOf<T>): T
188+
(...args: ArgumentsOf<T>): any
189+
}
190+
191+
// tslint:disable-next-line:ban-types
192+
type MethodKeysOf<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]
193+
// tslint:disable-next-line:ban-types
194+
type PropertyKeysOf<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]
195+
type ArgumentsOf<T> = T extends (...args: infer A) => any ? A : never
196+
interface MockWithArgs<T> extends Function, jest.MockInstance<T> {
197+
new (...args: ArgumentsOf<T>): T
198+
(...args: ArgumentsOf<T>): any
199+
}
200+
201+
type MockedFunction<T> = MockWithArgs<T> & { [K in keyof T]: T[K] }
202+
type MockedFunctionDeep<T> = MockWithArgs<T> & MockedObjectDeep<T>
203+
type MockedObject<T> = { [K in MethodKeysOf<T>]: MockedFunction<T[K]> } & { [K in PropertyKeysOf<T>]: T[K] }
204+
type MockedObjectDeep<T> = { [K in MethodKeysOf<T>]: MockedFunctionDeep<T[K]> } &
205+
{ [K in PropertyKeysOf<T>]: MaybeMockedDeep<T[K]> }
206+
207+
export type MockedDeep<T> = MockWithArgs<T> & { [K in MethodKeysOf<T>]: MockedDeep<T[K]> }
208+
export type Mocked<T> = MockWithArgs<T> & { [K in MethodKeysOf<T>]: Mocked<T[K]> }
209+
// tslint:disable-next-line:ban-types
210+
export type MaybeMockedDeep<T> = T extends Function ? MockedFunctionDeep<T> : T extends object ? MockedObjectDeep<T> : T
211+
// tslint:disable-next-line:ban-types
212+
export type MaybeMocked<T> = T extends Function ? MockedFunction<T> : T extends object ? MockedObject<T> : T

Diff for: ‎src/util/jsonable-value.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mocked } from '../__helpers__/mocks'
1+
import { mocked } from '../..'
22

33
import * as _json from './json'
44
import { JsonableValue } from './jsonable-value'
@@ -13,7 +13,7 @@ beforeEach(() => {
1313
jest.clearAllMocks()
1414
})
1515

16-
it('should cache the seralized value', () => {
16+
it('should cache the serialized value', () => {
1717
const jv = new JsonableValue({ foo: 'bar' })
1818
expect(jv.serialized).toBe('{"foo":"bar"}')
1919
expect(stringify).toHaveBeenCalledTimes(1)

Diff for: ‎src/util/testing.spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { mocked } from './testing'
2+
3+
describe('mocked', () => {
4+
it('should return unmodified input', () => {
5+
const subject = {}
6+
expect(mocked(subject)).toBe(subject)
7+
})
8+
})

Diff for: ‎src/util/testing.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { MaybeMocked, MaybeMockedDeep } from '../types'
2+
3+
// the typings test helper
4+
export function mocked<T>(item: T, deep?: false): MaybeMocked<T>
5+
export function mocked<T>(item: T, deep: true): MaybeMockedDeep<T>
6+
export function mocked<T>(item: T, _deep = false): MaybeMocked<T> | MaybeMockedDeep<T> {
7+
return item as any
8+
}

Diff for: ‎src/util/version-checkers.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// tslint:disable:max-line-length
2-
import { logTargetMock, mocked } from '../__helpers__/mocks'
2+
import { mocked } from '../..'
3+
import { logTargetMock } from '../__helpers__/mocks'
34

45
import * as _pv from './get-package-version'
56
import { VersionChecker, VersionCheckers } from './version-checkers'

0 commit comments

Comments
 (0)
Please sign in to comment.