Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions compatibility-checks/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ type TestRunner = {

test.describe('Verifies test runner compatibility', () => {
const failingExec = (command: string): string => {
process.env
const { FORCE_COLOR, ...environment } = { ...process.env, CI: '1', NO_COLOR: '1' } as Partial<Record<string, string>>
try {
execSync(command, { env: environment, stdio: 'pipe' })
Expand Down Expand Up @@ -43,11 +42,10 @@ test.describe('Verifies test runner compatibility', () => {
`Could not find the expected failure location in the output. Expected "${testRunner.failureLocationText}"`
)
assert.ok(
result.includes('SubstituteException: Expected'),
result.includes('SubstituteException: Call count mismatch'),
`Could not find the expected exception message in the output: ${result}`
)
})
})
})
})

30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@
"devDependencies": {
"@ava/typescript": "^3.0.1",
"@sinonjs/fake-timers": "^11.2.2",
"@types/node": "^18.19.22",
"@types/node": "^18.19.122",
"@types/sinonjs__fake-timers": "^8.1.5",
"ava": "^4.3.3",
"typescript": "^4.8.4"
"typescript": "^5.9.2"
},
"volta": {
"node": "18.19.1"
Expand Down
48 changes: 6 additions & 42 deletions spec/ClearSubstitute.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import test from 'ava'

import { Substitute, SubstituteOf } from '../src'
import { SubstituteNode } from '../src/SubstituteNode'
import { SubstituteNode, instance } from '../src/internals/SubstituteNode'

interface Calculator {
add(a: number, b: number): number
Expand All @@ -11,54 +11,18 @@ interface Calculator {
}

type InstanceReturningSubstitute<T> = SubstituteOf<T> & {
[SubstituteNode.instance]: SubstituteNode
[instance]: SubstituteNode
}

test('clears everything on a substitute', t => {
const calculator = Substitute.for<Calculator>() as InstanceReturningSubstitute<Calculator>
calculator.add(1, 1)
calculator.received().add(1, 1)
calculator.clearSubstitute()

t.is(calculator[SubstituteNode.instance].recorder.records.size, 0)
t.is(calculator[SubstituteNode.instance].recorder.indexedRecords.size, 0)

t.throws(() => calculator.received().add(1, 1))

// explicitly using 'all'
calculator.add(1, 1)
calculator.received().add(1, 1)
calculator.clearSubstitute('all')

t.is(calculator[SubstituteNode.instance].recorder.records.size, 0)
t.is(calculator[SubstituteNode.instance].recorder.indexedRecords.size, 0)

t.throws(() => calculator.received().add(1, 1))
})

test('clears received calls on a substitute', t => {
test.skip('clears received calls on a substitute', t => {
const calculator = Substitute.for<Calculator>() as InstanceReturningSubstitute<Calculator>
calculator.add(1, 1)
calculator.add(1, 1).returns(2)
calculator.clearSubstitute('receivedCalls')
calculator.clearReceivedCalls();

t.is(calculator[SubstituteNode.instance].recorder.records.size, 2)
t.is(calculator[SubstituteNode.instance].recorder.indexedRecords.size, 2)
t.is(calculator[instance].recorder.records.size, 2)
t.is(calculator[instance].recorder.indexedRecords.size, 2)

t.throws(() => calculator.received().add(1, 1))
t.is(2, calculator.add(1, 1))
})

test('clears return values on a substitute', t => {
const calculator = Substitute.for<Calculator>() as InstanceReturningSubstitute<Calculator>
calculator.add(1, 1)
calculator.add(1, 1).returns(2)
calculator.clearSubstitute('substituteValues')

t.is(calculator[SubstituteNode.instance].recorder.records.size, 2)
t.is(calculator[SubstituteNode.instance].recorder.indexedRecords.size, 2)

t.notThrows(() => calculator.received().add(1, 1))
// @ts-expect-error
t.true(calculator.add(1, 1)[SubstituteNode.instance] instanceof SubstituteNode)
})
4 changes: 2 additions & 2 deletions spec/RecordedArguments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import test from 'ava'
import { inspect } from 'util'

import { Arg } from '../src'
import { RecordedArguments } from '../src/RecordedArguments'
import { RecordedArguments } from '../src/internals/RecordedArguments'

const testObject = { 'foo': 'bar' }
const testArray = ['a', 1, true]
Expand Down Expand Up @@ -135,4 +135,4 @@ test('generates custom text representation', t => {
t.is(inspect(RecordedArguments.from([])), '()')
t.is(inspect(RecordedArguments.from([undefined])), 'undefined')
t.is(inspect(RecordedArguments.from([undefined, 1])), '(undefined, 1)')
})
})
8 changes: 4 additions & 4 deletions spec/Recorder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import test from 'ava'

import { Recorder } from '../src/Recorder'
import { RecordsSet } from '../src/RecordsSet'
import { Substitute } from '../src/Substitute'
import { SubstituteNodeBase } from '../src/SubstituteNodeBase'
import { Recorder } from '../src/internals/Recorder'
import { RecordsSet } from '../src/internals/RecordsSet'
import { Substitute } from '../src/api/Substitute'
import { SubstituteNodeBase } from '../src/internals/SubstituteNodeBase'

const nodeFactory = (key: string) => {
const node = Substitute.for<SubstituteNodeBase>()
Expand Down
4 changes: 2 additions & 2 deletions spec/RecordsSet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test, { ExecutionContext } from 'ava'

import { RecordsSet } from '../src/RecordsSet'
import { RecordsSet } from '../src/internals/RecordsSet'

const dataArray = [1, 2, 3]
function* dataArrayGenerator() {
Expand Down Expand Up @@ -63,4 +63,4 @@ test('applies and preserves the order of filter and map functions everytime the
t.deepEqual([...setWithFilter], [1, 3])
t.deepEqual([...setWithFilterAndMap], ['1', '3'])
t.deepEqual([...setWithFilterMapAndAnotherFilter], ['3'])
})
})
4 changes: 2 additions & 2 deletions spec/regression/Arguments.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test from 'ava'
import { Arg } from '../../src'
import { Argument } from '../../src/Arguments'
import { Argument } from '../../src/shared'

const testObject = { "foo": "bar" }
const testArray = ["a", 1, true]
Expand Down Expand Up @@ -107,4 +107,4 @@ test('should not match the argument with the predicate function using Arg.is.not

t.true(Arg.is.not<string>(x => x === 'foo').matches('bar'))
t.true(Arg.is.not<number>(x => x % 2 == 0).matches(3))
})
})
6 changes: 3 additions & 3 deletions spec/regression/didNotReceive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test from 'ava'
import { Substitute, Arg } from '../../src'
import { SubstituteException } from '../../src/SubstituteException'
import { SubstituteException } from '../../src/internals/SubstituteException'

interface Calculator {
add(a: number, b: number): number
Expand All @@ -13,7 +13,7 @@ test('not calling a method correctly asserts the call count', t => {
const calculator = Substitute.for<Calculator>()

calculator.didNotReceive().add(1, 1)
t.throws(() => calculator.received(1).add(1, 1), { instanceOf: SubstituteException })
t.throws(() => calculator.received().add(1, 1), { instanceOf: SubstituteException })
t.throws(() => calculator.received().add(Arg.all()), { instanceOf: SubstituteException })
})

Expand Down Expand Up @@ -49,4 +49,4 @@ test('not getting a property with mock correctly asserts the call count', t => {
calculator.didNotReceive().isEnabled
t.throws(() => calculator.received(1).isEnabled, { instanceOf: SubstituteException })
t.throws(() => calculator.received().isEnabled, { instanceOf: SubstituteException })
})
})
71 changes: 31 additions & 40 deletions spec/regression/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test from 'ava'

import { Substitute, Arg, SubstituteOf } from '../../src'
import { Substitute, Arg, SubstituteOf, received } from '../../src'

class Dummy {

Expand All @@ -20,15 +20,15 @@ export class Example {
set v(x: string | null | undefined) {
}

received(stuff: number | string) {
received(_stuff: string) {

}

returnPromise() {
return Promise.resolve(new Dummy())
}

foo(): string | undefined | null {
foo(_arg?: string): string | undefined | null {
return 'stuff'
}

Expand All @@ -47,22 +47,13 @@ function initialize() {

const textModifierRegex = /\x1b\[\d+m/g

test('class with method called \'received\' can be used for call count verification when proxies are suspended', t => {
initialize()

Substitute.disableFor(substitute).received(2)

t.throws(() => substitute.received(2).received(2))
t.notThrows(() => substitute.received(1).received(2))
})

test('class with method called \'received\' can be used for call count verification', t => {
initialize()
test('class with method called \'received\' can be used for call count verification when using symbols', t => {
const substitute = Substitute.for<Example>()

Substitute.disableFor(substitute).received('foo')
substitute.received("foo")

t.notThrows(() => substitute.received(1).received('foo'))
t.throws(() => substitute.received(2).received('foo'))
t.notThrows(() => substitute[received](1).received("foo"))
t.throws(() => substitute[received](2).received("foo"))
})

test('class string field set received', t => {
Expand All @@ -79,16 +70,16 @@ test('class string field set received', t => {
runLogic(substitute)


t.notThrows(() => substitute.received().v = 'hello')
t.notThrows(() => substitute.received(5).v = Arg.any())
t.notThrows(() => substitute.received().v = Arg.any())
t.notThrows(() => substitute.received(2).v = 'hello')
t.notThrows(() => substitute.received(2).v = Arg.is(x => typeof x === 'string' && x.indexOf('ll') > -1))
t.notThrows(() => substitute[received]().v = 'hello')
t.notThrows(() => substitute[received](5).v = Arg.any())
t.notThrows(() => substitute[received]().v = Arg.any())
t.notThrows(() => substitute[received](2).v = 'hello')
t.notThrows(() => substitute[received](2).v = Arg.is(x => typeof x === 'string' && x.indexOf('ll') > -1))

t.throws(() => substitute.received(2).v = Arg.any())
t.throws(() => substitute.received(1).v = Arg.any())
t.throws(() => substitute.received(1).v = Arg.is(x => typeof x === 'string' && x.indexOf('ll') > -1))
t.throws(() => substitute.received(3).v = 'hello')
t.throws(() => substitute[received](2).v = Arg.any())
t.throws(() => substitute[received](1).v = Arg.any())
t.throws(() => substitute[received](1).v = Arg.is(x => typeof x === 'string' && x.indexOf('ll') > -1))
t.throws(() => substitute[received](3).v = 'hello')
})

test('resolving promises works', async t => {
Expand Down Expand Up @@ -117,24 +108,24 @@ test('class method received', t => {
void substitute.c('hi', 'there')
void substitute.c('hi', 'there')

t.notThrows(() => substitute.received(4).c('hi', 'there'))
t.notThrows(() => substitute.received(1).c('hi', 'the1re'))
t.notThrows(() => substitute.received().c('hi', 'there'))
t.notThrows(() => substitute[received](4).c('hi', 'there'))
t.notThrows(() => substitute[received](1).c('hi', 'the1re'))
t.notThrows(() => substitute[received]().c('hi', 'there'))

const expectedMessage = 'Call count mismatch in @Substitute.c:\n' +
`Expected to receive 7 method calls matching c('hi', 'there'), but received 4.\n` +
'All property or method calls to @Substitute.c received so far:\n' +
`› ✔ @Substitute.c('hi', 'there')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:114:18)\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:105:18)\n` +
`› ✘ @Substitute.c('hi', 'the1re')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:115:18)\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:106:18)\n` +
`› ✔ @Substitute.c('hi', 'there')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:116:18)\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:107:18)\n` +
`› ✔ @Substitute.c('hi', 'there')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:117:18)\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:108:18)\n` +
`› ✔ @Substitute.c('hi', 'there')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:118:18)\n`
const { message } = t.throws(() => { substitute.received(7).c('hi', 'there') })
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:109:18)\n`
const { message } = t.throws(() => { substitute[received](7).c('hi', 'there') })
t.is(message.replace(textModifierRegex, ''), expectedMessage)
})

Expand All @@ -144,16 +135,16 @@ test('received call matches after partial mocks using property instance mimicks'
substitute.d.mimicks(() => instance.d)
substitute.c('lala', 'bar')

substitute.received(1).c('lala', 'bar')
substitute.received(1).c('lala', 'bar')
substitute[received](1).c('lala', 'bar')
substitute[received](1).c('lala', 'bar')

t.notThrows(() => substitute.received(1).c('lala', 'bar'))
t.notThrows(() => substitute[received](1).c('lala', 'bar'))
const expectedMessage = 'Call count mismatch in @Substitute.c:\n' +
`Expected to receive 2 method calls matching c('lala', 'bar'), but received 1.\n` +
'All property or method calls to @Substitute.c received so far:\n' +
`› ✔ @Substitute.c('lala', 'bar')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:145:13)\n`
const { message } = t.throws(() => substitute.received(2).c('lala', 'bar'))
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:136:13)\n`
const { message } = t.throws(() => substitute[received](2).c('lala', 'bar'))
t.is(message.replace(textModifierRegex, ''), expectedMessage)
t.deepEqual(substitute.d, 1337)
})
Expand Down
Loading