Skip to content

Commit 899060d

Browse files
committed
Rewrite the td.function() rewrite
Key insight here was that the public API really shouldn't be the same as the private API, because we want to encourage passing around Double objects internally and only expose the fake externally. Additionally, the imitation module is doing a lot of the heavy lifting now and this isn't nearly as complicated as it was once going to be.
1 parent 00b8761 commit 899060d

14 files changed

+150
-121
lines changed

src/function/create.js

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,6 @@
1-
import _ from '../wrap/lodash'
2-
31
import Double from '../value/double'
4-
import CallLog from '../value/call-log'
5-
import Call from '../value/call'
6-
import StubbingRegister from '../value/stubbing-register'
7-
8-
export default (nameOrFunc) => {
9-
const name = deriveName(nameOrFunc)
10-
const real = _.isFunction(nameOrFunc) ? nameOrFunc : null
11-
const double = new Double(name, real, _.tap(function testDouble (...args) {
12-
const call = new Call(this, args)
13-
CallLog.instance.log(double, call)
14-
return StubbingRegister.instance.satisfy(double, call)
15-
}, (fakeFunction) => {
16-
fakeFunction.toString = () =>
17-
double.fullName == null ? '[test double (unnamed)]' : `[test double for "${double.fullName}"]`
18-
}))
19-
return double
20-
}
2+
import generateFakeFunction from './generate-fake-function'
213

22-
const deriveName = (nameOrFunc) => {
23-
const name = _.isFunction(nameOrFunc) ? nameOrFunc.name : nameOrFunc
24-
return _.isEmpty(name) ? null : name
4+
export default function create (name, real, parent) {
5+
return Double.create(name, real, parent, generateFakeFunction)
256
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import CallLog from '../value/call-log'
2+
import Call from '../value/call'
3+
import StubbingRegister from '../value/stubbing-register'
4+
5+
export default function generateFakeFunction (double) {
6+
const testDouble = function testDouble (...args) {
7+
const call = new Call(this, args)
8+
CallLog.instance.log(double, call)
9+
return StubbingRegister.instance.satisfy(double, call)
10+
}
11+
testDouble.toString = double.toString.bind(double)
12+
13+
return testDouble
14+
}

src/function/index.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import _ from '../wrap/lodash'
22

33
import create from './create'
4-
import imitate from '../imitate'
5-
import remember from './remember'
64

7-
export default (nameOrFunc) => {
8-
if (_.isFunction(nameOrFunc)) return imitate(nameOrFunc)
9-
const double = create(nameOrFunc)
10-
remember(double)
11-
return double.fake
5+
export default function func (nameOrFunc) {
6+
if (_.isFunction(nameOrFunc)) {
7+
return create(_.isEmpty(nameOrFunc.name) ? null : nameOrFunc.name, nameOrFunc).fake
8+
} else {
9+
return create(nameOrFunc, null).fake
10+
}
1211
}

src/function/remember.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/value/double.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import _ from '../wrap/lodash'
22

33
export default class Double {
4-
constructor (name, real, fake) {
4+
static create (name, real, parent, fakeCreator) {
5+
const double = new Double(name, real, parent)
6+
if (fakeCreator) double.fake = fakeCreator(double)
7+
return double
8+
}
9+
10+
constructor (name, real, parent) {
511
this.name = name
612
this.real = real
7-
this.fake = fake
8-
this.parent = undefined
913
this.children = new Set()
14+
if (parent) {
15+
this.parent = parent
16+
parent.addChild(this)
17+
}
1018
}
1119

1220
addChild (child) {
@@ -25,4 +33,8 @@ export default class Double {
2533
if (!this.parent) return []
2634
return this.parent.ancestors.concat(this.parent)
2735
}
36+
37+
toString () {
38+
return this.fullName == null ? '[test double (unnamed)]' : `[test double for "${this.fullName}"]`
39+
}
2840
}

test/helper.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ var CallLog = require('../src/value/call-log').default
99
var StubbingRegister = require('../src/value/stubbing-register').default
1010

1111
module.exports = {
12-
beforeAll: function () {},
12+
beforeAll: function () {
13+
require('./support/custom-assertions').default(assert)
14+
},
1315
beforeEach: function () {},
1416
afterEach: function () {
1517
td.reset()

test/support/custom-assertions.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function customAssertions (assert) {
2+
assert.deepEqualSet = (actual, expected) => {
3+
assert.deepEqual(Array.from(actual), expected)
4+
}
5+
}

test/unit/function/create.test.js

Lines changed: 10 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,17 @@
1-
import Double from '../../../src/value/double'
2-
import CallLog from '../../../src/value/call-log'
3-
import StubbingRegister from '../../../src/value/stubbing-register'
4-
import Stubbing from '../../../src/value/stubbing'
5-
import subject from '../../../src/function/create'
6-
1+
let Double, generateFakeFunction,subject
72
module.exports = {
8-
'passed a string name': () => {
9-
const result = subject('foo')
10-
11-
assert(result instanceof Double)
12-
assert.strictEqual(result.real, null)
13-
assert.equal(result.name, 'foo')
14-
assert.equal(typeof result.fake, 'function')
15-
assert.equal(result.fake.toString(), '[test double for "foo"]')
16-
},
17-
'passed a function with a name': () => {
18-
function bar () {}
19-
const result = subject(bar)
20-
21-
assert.equal(result.real, bar)
22-
assert.equal(result.name, 'bar')
23-
assert.equal(result.fake.toString(), '[test double for "bar"]')
24-
},
25-
'passed an unnamed function': () => {
26-
const result = subject(function () {})
27-
28-
assert.strictEqual(result.name, null)
29-
assert.equal(result.fake.toString(), '[test double (unnamed)]')
30-
},
31-
'passed nothing': () => {
32-
const result = subject()
3+
beforeEach: () => {
4+
Double = td.replace('../../../src/value/double').default
5+
generateFakeFunction = td.replace('../../../src/function/generate-fake-function').default
336

34-
assert.strictEqual(result.name, null)
35-
assert.strictEqual(result.real, null)
36-
assert.equal(result.fake.toString(), '[test double (unnamed)]')
7+
subject = require('../../../src/function/create').default
378
},
38-
'the fake function itself': {
39-
'logs calls': () => {
40-
const double = subject()
9+
'puts the lime in the coconut': () => {
10+
td.when(Double.create('a name', 'a real', 'a parent', generateFakeFunction)).thenReturn('yasss')
4111

42-
double.fake.call('fake this', 1, 2, 3)
12+
const result = subject('a name', 'a real', 'a parent', generateFakeFunction)
4313

44-
var calls = CallLog.instance.for(double)
45-
assert.equal(calls.length, 1)
46-
assert.equal(calls[0].context, 'fake this')
47-
assert.deepEqual(calls[0].args, [1, 2, 3])
48-
},
49-
'registers stubbing': () => {
50-
const double = subject()
51-
const stubbing = new Stubbing('return', ['a', 'b'], ['c'])
52-
StubbingRegister.instance.add(double, stubbing)
53-
54-
const result = double.fake('a', 'b')
55-
56-
assert.equal(result, 'c')
57-
}
58-
},
59-
'toString supports mutation (necessary sometimes for td.replace() to depend on td.func()': () => {
60-
const double = subject()
61-
62-
double.name = 'new name'
63-
64-
assert.equal(double.name, 'new name')
65-
assert.equal(double.fake.toString(), '[test double for "new name"]')
14+
assert.equal(result, 'yasss')
6615
}
16+
6717
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Double from '../../../src/value/double'
2+
import CallLog from '../../../src/value/call-log'
3+
import StubbingRegister from '../../../src/value/stubbing-register'
4+
import Stubbing from '../../../src/value/stubbing'
5+
import subject from '../../../src/function/generate-fake-function'
6+
7+
module.exports = {
8+
'the fake function itself': {
9+
'logs calls': () => {
10+
const double = Double.create()
11+
12+
subject(double).call('fake this', 1, 2, 3)
13+
14+
var calls = CallLog.instance.for(double)
15+
assert.equal(calls.length, 1)
16+
assert.equal(calls[0].context, 'fake this')
17+
assert.deepEqual(calls[0].args, [1, 2, 3])
18+
},
19+
'registers stubbing': () => {
20+
const double = Double.create()
21+
const stubbing = new Stubbing('return', ['a', 'b'], ['c'])
22+
StubbingRegister.instance.add(double, stubbing)
23+
24+
const result = subject(double)('a', 'b')
25+
26+
assert.equal(result, 'c')
27+
}
28+
},
29+
'sets toString to that of the double': () => {
30+
const double = Double.create('name')
31+
32+
const result = subject(double)
33+
34+
assert.equal(result.toString(), double.toString())
35+
}
36+
}

test/unit/function/index.test.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
import Double from '../../../src/value/double'
22

3-
let create, imitate, remember, subject
3+
let create, subject
44
module.exports = {
55
beforeEach: () => {
66
create = td.replace('../../../src/function/create').default
7-
imitate = td.replace('../../../src/imitate').default
8-
remember = td.replace('../../../src/function/remember').default
97

108
subject = require('../../../src/function/index').default
119
},
1210
'pass in a name': () => {
13-
const double = new Double(null, null, 'fake thing')
14-
td.when(create('foo')).thenReturn(double)
11+
const double = Double.create(null, null, null, () => 'fake thing')
12+
td.when(create('foo', null)).thenReturn(double)
1513

1614
const result = subject('foo')
1715

18-
assert.equal(result, double.fake)
19-
td.verify(remember(double))
16+
assert.equal(result, 'fake thing')
2017
},
21-
'pass in a function': () => {
18+
'pass in a named function': () => {
2219
function bar () {}
23-
td.when(imitate(bar)).thenReturn('fake bar')
20+
const double = Double.create(null, null, null, () => 'fake bar')
21+
td.when(create('bar', bar)).thenReturn(double)
2422

2523
const result = subject(bar)
2624

2725
assert.equal(result, 'fake bar')
26+
},
27+
'pass in an unnamed function': () => {
28+
const unnamedFunc = eval('(function () {})') //eslint-disable-line
29+
const double = Double.create(null, null, null, () => 'fake')
30+
td.when(create(null, unnamedFunc)).thenReturn(double)
31+
32+
const result = subject(unnamedFunc)
33+
34+
assert.equal(result, 'fake')
2835
}
2936
}

0 commit comments

Comments
 (0)