Skip to content

Commit

Permalink
feat: symbols for methods
Browse files Browse the repository at this point in the history
- removed Substitute.disableFor
  • Loading branch information
ffMathy committed Mar 11, 2024
1 parent cdd3d04 commit ee5c4fd
Show file tree
Hide file tree
Showing 21 changed files with 262 additions and 278 deletions.
3 changes: 2 additions & 1 deletion spec/ClearSubstitute.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import test from 'ava'

import { Substitute, SubstituteOf, clearReceivedCalls, received } from '../src'
import { SubstituteNode } from '../src/SubstituteNode'
import { returns } from '../src/Transformations'

interface Calculator {
add(a: number, b: number): number
Expand All @@ -17,7 +18,7 @@ type InstanceReturningSubstitute<T> = SubstituteOf<T> & {
test('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.add(1, 1)[returns](2)
calculator[clearReceivedCalls]();

t.is(calculator[SubstituteNode.instance].recorder.records.size, 2)
Expand Down
3 changes: 2 additions & 1 deletion spec/Recorder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { Recorder } from '../src/Recorder'
import { RecordsSet } from '../src/RecordsSet'
import { Substitute } from '../src/Substitute'
import { SubstituteNodeBase } from '../src/SubstituteNodeBase'
import { returns } from '../src/Transformations'

const nodeFactory = (key: string) => {
const node = Substitute.for<SubstituteNodeBase>()
node.key.returns(key)
node.key[returns](key)
return node
}

Expand Down
5 changes: 3 additions & 2 deletions spec/regression/didNotReceive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import test from 'ava'
import { Substitute, Arg, didNotReceive, received } from '../../src'
import { SubstituteException } from '../../src/SubstituteException'
import { returns } from '../../src/Transformations'

interface Calculator {
add(a: number, b: number): number
Expand Down Expand Up @@ -35,7 +36,7 @@ test('not setting a property correctly asserts the call count', t => {

test('not calling a method with mock correctly asserts the call count', t => {
const calculator = Substitute.for<Calculator>()
calculator.add(1, 1).returns(2)
calculator.add(1, 1)[returns](2)

calculator[didNotReceive]().add(1, 1)
t.throws(() => calculator[received](1).add(1, 1), { instanceOf: SubstituteException })
Expand All @@ -44,7 +45,7 @@ test('not calling a method with mock correctly asserts the call count', t => {

test('not getting a property with mock correctly asserts the call count', t => {
const calculator = Substitute.for<Calculator>()
calculator.isEnabled.returns(true)
calculator.isEnabled[returns](true)

calculator[didNotReceive]().isEnabled
t.throws(() => calculator[received](1).isEnabled, { instanceOf: SubstituteException })
Expand Down
61 changes: 26 additions & 35 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, mimicks, resolves, returns } from '../../src'

class Dummy {

Expand Down Expand Up @@ -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 => {
test('class with method called \'received\' can be used for call count verification when using symbols', t => {
initialize()

Substitute.disableFor(substitute).received(2)
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()

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

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

test('class string field set received', t => {
Expand All @@ -79,30 +70,30 @@ 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 => {
initialize()

substitute.returnPromise().resolves(1338)
substitute.returnPromise()[resolves](1338)

t.is(1338, await substitute.returnPromise() as number)
})

test('class void returns', t => {
initialize()

substitute.foo().returns(void 0, null)
substitute.foo()[returns](void 0, null)

t.is(substitute.foo(), void 0)
t.is(substitute.foo(), null)
Expand All @@ -117,9 +108,9 @@ 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 = 'Expected 7 calls to the method c with arguments [\'hi\', \'there\'], but received 4 of such calls.\n' +
'All calls received to method c:\n' +
Expand All @@ -128,32 +119,32 @@ test('class method received', t => {
'-> call with arguments [\'hi\', \'there\']\n' +
'-> call with arguments [\'hi\', \'there\']\n' +
'-> call with arguments [\'hi\', \'there\']'
const { message } = t.throws(() => { substitute.received(7).c('hi', 'there') })
const { message } = t.throws(() => { substitute[received](7).c('hi', 'there') })
t.is(message.replace(textModifierRegex, ''), expectedMessage)
})

test('received call matches after partial mocks using property instance mimicks', t => {
initialize()

substitute.d.mimicks(() => instance.d)
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 = 'Expected 2 calls to the method c with arguments [\'lala\', \'bar\'], but received 1 of such calls.\n' +
'All calls received to method c:\n' +
'-> call with arguments [\'lala\', \'bar\']'
const { message } = t.throws(() => substitute.received(2).c('lala', 'bar'))
const { message } = t.throws(() => substitute[received](2).c('lala', 'bar'))
t.is(message.replace(textModifierRegex, ''), expectedMessage)
t.deepEqual(substitute.d, 1337)
})

test('partial mocks using property instance mimicks', t => {
initialize()

substitute.d.mimicks(() => instance.d)
substitute.d[mimicks](() => instance.d)

t.deepEqual(substitute.d, 1337)
})
6 changes: 3 additions & 3 deletions spec/regression/issues/11.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'ava'
import { Substitute, Arg } from '../../../src'
import { Substitute, Arg, received, returns } from '../../../src'

type Addands = {
op1: number
Expand All @@ -14,12 +14,12 @@ class RealCalculator {

test('issue 11: arg.is is only called once', async t => {
let mockedCalculator = Substitute.for<RealCalculator>()
mockedCalculator.add(Arg.any()).returns(4)
mockedCalculator.add(Arg.any())[returns](4)

let count = 0
mockedCalculator.add({ op1: 1, op2: 2 })

mockedCalculator.received(1).add(Arg.is(a => {
mockedCalculator[received](1).add(Arg.is(a => {
count++
return a.op1 === 1 && a.op2 === 2
}))
Expand Down
18 changes: 9 additions & 9 deletions spec/regression/issues/178.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import test, { ExecutionContext, ThrowsExpectation } from 'ava'
import * as fakeTimers from '@sinonjs/fake-timers'
import { types } from 'util'

import { Substitute } from '../../../src'
import { Substitute, didNotReceive, received, returns } from '../../../src'
import { SubstituteException } from '../../../src/SubstituteException'

interface Library {
Expand All @@ -24,17 +24,17 @@ const throwsUncaughtException = (cb: () => any, t: ExecutionContext, expectation

test('can substitute callable interfaces', async t => {
const lib = Substitute.for<Library>()
lib.subSection().returns('subSection as method')
lib.subSection.returns({ subMethod: () => 'subSection as property' } as Subsection)
lib.subSection()[returns]('subSection as method')
lib.subSection[returns]({ subMethod: () => 'subSection as property' } as Subsection)

t.is('subSection as method', lib.subSection())
t.true(types.isProxy(lib.subSection), 'Expected proxy: given the context, it\'s not possible to determine the property type')
t.is('subSection as property', lib.subSection.subMethod())

lib.received().subSection()
lib.received(1).subSection()
lib.received(2).subSection
t.throws(() => lib.didNotReceive().subSection(), { instanceOf: SubstituteException })
t.throws(() => lib.received(2).subSection(), { instanceOf: SubstituteException })
throwsUncaughtException(() => lib.received(3).subSection, t, { instanceOf: SubstituteException })
lib[received]().subSection()
lib[received](1).subSection()
lib[received](2).subSection
t.throws(() => lib[didNotReceive]().subSection(), { instanceOf: SubstituteException })
t.throws(() => lib[received](2).subSection(), { instanceOf: SubstituteException })
throwsUncaughtException(() => lib[received](3).subSection, t, { instanceOf: SubstituteException })
})
6 changes: 3 additions & 3 deletions spec/regression/issues/23.test.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 { Substitute, Arg, received, mimicks } from '../../../src'

interface CalculatorInterface {
add(a: number, b: number): number
Expand All @@ -14,12 +14,12 @@ test('issue 23: mimick received should not call method', t => {

let calls = 0

mockedCalculator.add(Arg.all()).mimicks((a, b) => {
mockedCalculator.add(Arg.all())[mimicks]((a, b) => {
t.deepEqual(++calls, 1, 'mimick called twice')
return a + b
})

mockedCalculator.add(1, 1) // ok

mockedCalculator.received(1).add(1, 1) // not ok, calls mimick func
mockedCalculator[received](1).add(1, 1) // not ok, calls mimick func
})
8 changes: 4 additions & 4 deletions spec/regression/issues/36.test.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 { Substitute, Arg, received, returns } from '../../../src'

class Key {
private constructor(private _value: string) { }
Expand Down Expand Up @@ -49,7 +49,7 @@ class Service {

test('issue 36 - promises returning object with properties', async t => {
const emptyFetch = Substitute.for<IFetch>()
emptyFetch.getUpdates(Key.create()).returns(Promise.resolve<IData>(IData.create()))
emptyFetch.getUpdates(Key.create())[returns](Promise.resolve<IData>(IData.create()))
const result = await emptyFetch.getUpdates(Key.create())
t.true(result.serverCheck instanceof Date, 'given date is instanceof Date')
t.deepEqual(result.data, [1], 'arrays are deep equal')
Expand All @@ -58,12 +58,12 @@ test('issue 36 - promises returning object with properties', async t => {
test('using objects or classes as arguments should be able to match mock', async t => {
const db = Substitute.for<IFetch>()
const data = IData.create()
db.getUpdates(Key.create()).returns(Promise.resolve(data))
db.getUpdates(Key.create())[returns](Promise.resolve(data))
const service = new Service(db)

await service.handle(Key.create())

db.received(1).storeUpdates(Arg.is((arg: IData) =>
db[received](1).storeUpdates(Arg.is((arg: IData) =>
arg.serverCheck instanceof Date &&
arg instanceof IData &&
arg.data[0] === 100
Expand Down
6 changes: 3 additions & 3 deletions spec/regression/issues/45.test.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 { Substitute, Arg, received } from '../../../src'

class DependencyClass {
public methodOne() { }
Expand Down Expand Up @@ -30,7 +30,7 @@ test('issue 45 Checking received calls off at times', async t => {
subject.callToMethodTwo()

t.notThrows(() => {
mock.received(1).methodOne()
mock.received(1).methodTwo(Arg.is(x => x === 'string'))
mock[received](1).methodOne()
mock[received](1).methodTwo(Arg.is(x => x === 'string'))
})
})
8 changes: 4 additions & 4 deletions spec/regression/issues/59.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test from 'ava'

import { Substitute } from '../../../src'
import { Substitute, received, returns } from '../../../src'

interface IEcho {
echo(a: string): string
Expand All @@ -9,10 +9,10 @@ interface IEcho {

test('issue 59 - Mock function with optional parameters', (t) => {
const echoer = Substitute.for<IEcho>()
echoer.maybeEcho('foo').returns('bar')
echoer.maybeEcho().returns('baz')
echoer.maybeEcho('foo')[returns]('bar')
echoer.maybeEcho()[returns]('baz')

t.is('bar', echoer.maybeEcho('foo'))
echoer.received().maybeEcho('foo')
echoer[received]().maybeEcho('foo')
t.is('baz', echoer.maybeEcho())
})
19 changes: 10 additions & 9 deletions spec/regression/mimicks.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import test from 'ava'
import { Substitute, Arg } from '../../src'
import { mimicks } from '../../src/Transformations'

interface Calculator {
add(a: number, b: number): number
Expand All @@ -16,16 +17,16 @@ test('mimicks a method with specific arguments', t => {
const calculator = Substitute.for<Calculator>()
const addMimick = (a: number, b: number) => a + b

calculator.add(1, 1).mimicks(addMimick)
calculator.add(1, 1)[mimicks](addMimick)
t.is(2, calculator.add(1, 1))
})

test('mimicks a method with specific and conditional arguments', t => {
const calculator = Substitute.for<Calculator>()
const addMimick = (a: number, b: number) => a + b

calculator.add(Arg.any('number'), Arg.is((input: number) => input >= 0 && input <= 10)).mimicks(addMimick)
calculator.add(42, -42).mimicks((a: number, b: number) => 0)
calculator.add(Arg.any('number'), Arg.is((input: number) => input >= 0 && input <= 10))[mimicks](addMimick)
calculator.add(42, -42)[mimicks]((a: number, b: number) => 0)

t.is(1240, calculator.add(1234, 6))
t.is(0, calculator.add(42, -42))
Expand All @@ -36,7 +37,7 @@ test('mimicks a method with Arg.all', t => {
const addMimick = (a: number, b: number) => a + b


calculator.add(Arg.all()).mimicks(addMimick)
calculator.add(Arg.all())[mimicks](addMimick)
t.is(100, calculator.add(42, 58))
})

Expand All @@ -45,9 +46,9 @@ test('mimicks a method with optional arguments', t => {
const multiplyOneArgMimicks = (a: number) => a * a
const multiplyMimicks = (a: number, b?: number) => a * (b ?? 0)

calculator.multiply(0, Arg.is((b: number) => b > 10 && b < 20)).mimicks(multiplyMimicks)
calculator.multiply(Arg.any('number'), Arg.is((b: number) => b === 2)).mimicks(multiplyMimicks)
calculator.multiply(2).mimicks(multiplyOneArgMimicks)
calculator.multiply(0, Arg.is((b: number) => b > 10 && b < 20))[mimicks](multiplyMimicks)
calculator.multiply(Arg.any('number'), Arg.is((b: number) => b === 2))[mimicks](multiplyMimicks)
calculator.multiply(2)[mimicks](multiplyOneArgMimicks)

t.is(0, calculator.multiply(0, 13))
t.is(84, calculator.multiply(42, 2))
Expand All @@ -57,8 +58,8 @@ test('mimicks a method with optional arguments', t => {
test('mimicks a method where it\'s only argument is optional', t => {
const calculator = Substitute.for<Calculator>()

calculator.viewResult().mimicks(() => 0)
calculator.viewResult(3).mimicks(() => 42)
calculator.viewResult()[mimicks](() => 0)
calculator.viewResult(3)[mimicks](() => 42)

t.is(0, calculator.viewResult())
t.is(42, calculator.viewResult(3))
Expand Down
Loading

0 comments on commit ee5c4fd

Please sign in to comment.