-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sinon): replace @golevelup/ts-sinon with native mocking function…
…ality Removed @golevelup/ts-sinon, added native auto mocking functionality instead
- Loading branch information
Showing
5 changed files
with
185 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
import { mock } from './mock.static'; | ||
|
||
interface ArbitraryMock { | ||
id: number; | ||
someValue?: boolean | null; | ||
getNumber: () => number; | ||
getNumberWithMockArg: (mock: never) => number; | ||
getSomethingWithArgs: (arg1: number, arg2: number) => number; | ||
getSomethingWithMoreArgs: (arg1: number, arg2: number, arg3: number) => number; | ||
} | ||
|
||
class TestClass implements ArbitraryMock { | ||
readonly id: number; | ||
|
||
constructor(id: number) { | ||
this.id = id; | ||
} | ||
|
||
public ofAnother(test: TestClass) { | ||
return test.getNumber(); | ||
} | ||
|
||
public getNumber() { | ||
return this.id; | ||
} | ||
|
||
public getNumberWithMockArg(mock: never) { | ||
return this.id; | ||
} | ||
|
||
public getSomethingWithArgs(arg1: number, arg2: number) { | ||
return this.id; | ||
} | ||
|
||
public getSomethingWithMoreArgs(arg1: number, arg2: number, arg3: number) { | ||
return this.id; | ||
} | ||
} | ||
|
||
describe('Mocking Proxy Mechanism Unit Spec', () => { | ||
describe('basic functionality', () => { | ||
test('should allow assignment to itself even with private parts', () => { | ||
const mockObject = mock<TestClass>(); | ||
new TestClass(1).ofAnother(mockObject); | ||
expect(mockObject.getNumber.callCount).toBe(1); | ||
}); | ||
|
||
test('should create jest.fn() without any invocation', () => { | ||
const mockObject = mock<ArbitraryMock>(); | ||
expect(mockObject.getNumber.callCount).toBe(0); | ||
}); | ||
|
||
test('should register invocations correctly', () => { | ||
const mockObject = mock<ArbitraryMock>(); | ||
mockObject.getNumber(); | ||
mockObject.getNumber(); | ||
expect(mockObject.getNumber.callCount).toBe(2); | ||
}); | ||
}); | ||
|
||
describe('mock return values and arguments', () => { | ||
test('should allow mocking a return value', () => { | ||
const mockObject = mock<ArbitraryMock>(); | ||
mockObject.getNumber.returns(12); | ||
expect(mockObject.getNumber()).toBe(12); | ||
}); | ||
|
||
test('should allow specifying arguments', () => { | ||
const mockObject = mock<ArbitraryMock>(); | ||
mockObject.getSomethingWithArgs(1, 2); | ||
expect(mockObject.getSomethingWithArgs.calledWithExactly(1, 2)).toBeTruthy(); | ||
}); | ||
}); | ||
|
||
describe('mock properties', () => { | ||
test('should allow setting properties', () => { | ||
const mockObject = mock<ArbitraryMock>(); | ||
mockObject.id = 17; | ||
expect(mockObject.id).toBe(17); | ||
}); | ||
|
||
test('should allow setting boolean properties to false or null', () => { | ||
const mockObject = mock<ArbitraryMock>({ someValue: false }); | ||
const mockObj2 = mock<ArbitraryMock>({ someValue: null }); | ||
expect(mockObject.someValue).toBe(false); | ||
expect(mockObj2.someValue).toBe(null); | ||
}); | ||
|
||
test('should allow setting properties to undefined explicitly', () => { | ||
const mockObject = mock<ArbitraryMock>({ someValue: undefined }); | ||
expect(mockObject.someValue).toBe(undefined); | ||
}); | ||
}); | ||
|
||
describe('mock implementation', () => { | ||
test('should allow providing mock implementations for properties', () => { | ||
const mockObject = mock<TestClass>({ id: 61 }); | ||
expect(mockObject.id).toBe(61); | ||
}); | ||
|
||
test('should allow providing mock implementations for functions', () => { | ||
const mockObject = mock<TestClass>({ getNumber: () => 150 }); | ||
expect(mockObject.getNumber()).toBe(150); | ||
}); | ||
}); | ||
|
||
describe('promises', () => { | ||
test('should successfully use mock for promises resolving', async () => { | ||
const mockObject = mock<ArbitraryMock>(); | ||
mockObject.id = 17; | ||
const promiseMockObj = Promise.resolve(mockObject); | ||
|
||
await expect(promiseMockObj).resolves.toBeDefined(); | ||
await expect(promiseMockObj).resolves.toMatchObject({ id: 17 }); | ||
}); | ||
|
||
test('should successfully use mock for promises rejecting', async () => { | ||
const mockError = mock<Error>(); | ||
mockError.message = '17'; | ||
const promiseMockObj = Promise.reject(mockError); | ||
|
||
await expect(promiseMockObj).rejects.toBeDefined(); | ||
await expect(promiseMockObj).rejects.toBe(mockError); | ||
await expect(promiseMockObj).rejects.toHaveProperty('message', '17'); | ||
}); | ||
}); | ||
|
||
describe('mocking a date objects', () => { | ||
test('should allow calling native date object methods', () => { | ||
const mockObject = mock({ date: new Date('2000-01-15') }); | ||
expect(mockObject.date.getFullYear()).toBe(2000); | ||
expect(mockObject.date.getMonth()).toBe(0); | ||
expect(mockObject.date.getDate()).toBe(15); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { SinonStubbedInstance, stub } from 'sinon'; | ||
|
||
type PropertyType = string | number | symbol; | ||
|
||
const createHandler = () => ({ | ||
get: (target: SinonStubbedInstance<any>, property: PropertyType) => { | ||
if (!(property in target)) { | ||
if (property === 'then') { | ||
return undefined; | ||
} | ||
|
||
if (property === Symbol.iterator) { | ||
return target[property as never]; | ||
} | ||
|
||
target[property as string] = stub(); | ||
} | ||
|
||
if (target instanceof Date && typeof target[property as never] === 'function') { | ||
return (target[property as never] as SinonStubbedInstance<any>).bind(target); | ||
} | ||
|
||
return target[property as string]; | ||
}, | ||
}); | ||
|
||
const applyMockImplementation = (initialObject: Record<string, any>) => { | ||
const proxy = new Proxy<SinonStubbedInstance<any>>(initialObject, createHandler()); | ||
|
||
for (const key of Object.keys(initialObject)) { | ||
if (typeof initialObject[key] === 'object' && initialObject[key] !== null) { | ||
proxy[key] = applyMockImplementation(initialObject[key]); | ||
} else { | ||
proxy[key] = initialObject[key]; | ||
} | ||
} | ||
|
||
return proxy; | ||
}; | ||
|
||
export const mock = <T>(mockImpl: Partial<T> = {} as Partial<T>): SinonStubbedInstance<T> => { | ||
return applyMockImplementation(mockImpl); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters