Created
October 26, 2011 08:02
-
-
Save grncdr/1315749 to your computer and use it in GitHub Desktop.
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
vows = require 'vows' | |
assert = require 'assert' | |
{mock, expect, lastCall, verify, pred, isDeep, any, verifyNoMore} = require './mtfw' | |
suite = vows.describe('mtfw core').addBatch "A mock will": | |
topic: -> mock() | |
"be an object": (m) -> assert.isObject m | |
"create new mocks when properties are accessed": -> | |
m1 = mock() | |
m2 = m1.someProp | |
assert.ok m2, "Property is defined" | |
assert.notStrictEqual m2, m1 | |
assert.isObject m2 | |
assert.equal m2, m1.someProp | |
"allow you to create Expectations": | |
topic: -> expect mock() | |
"with a list of arguments": (e) -> | |
assert.deepEqual e.args, [] | |
"that can be repeated": -> | |
m = mock() | |
expect(m).times(3).return 1 | |
assert.deepEqual [m(), m(), m(), m()], [1, 1, 1, undefined] | |
"that can save a return value": | |
topic: -> m = mock(); expect(m).return(1); m | |
"that is returned when the mock is called": (m) -> assert.equal m(), 1 | |
"that can save a function": | |
topic: -> | |
m = mock() | |
expect(m, 1, 2, 3).then((args...) -> args.reverse()) | |
m | |
"that is called in place of the mock": (m) -> | |
assert.deepEqual m(1,2,3), [3,2,1] | |
assert.ok lastCall(m), "expected calls are logged" | |
"record calls": -> | |
m = mock() | |
m(3,2,1) | |
lc = lastCall(m) | |
assert.isObject(lc) | |
args = lc.args | |
assert.deepEqual args, [3,2,1] | |
"return undefined when called": | |
topic: -> m = mock(); assert.isUndefined m(); m | |
"unless told otherwise": (m) -> | |
expect(m).return 1 | |
assert.equal m(), 1 | |
"allow you to verify unexpected calls": | |
topic: -> 1 # No shared topic | |
"arguments can be verified via identity": -> | |
m = mock(); m('string', 12) | |
verify(m, 'string', 12) | |
"arguments can be verified by predicates": -> | |
m = mock(); m 'string', 4, [1,2,3] | |
verify(m, | |
pred((s) -> s[0] == 's'), | |
pred((n) -> n > 0), | |
pred((a) -> Array.isArray(a) && a.length == 3)) | |
"structured arguments can be verified": -> | |
m = mock(); m [1,2,3] | |
verify m, isDeep [1,2,3] | |
"structured arguments can be verified with predicates": (m) -> | |
m = mock(); m [1,2,3] | |
verify m, isDeep [pred((n) -> n % 2), any, any] | |
suite.export(module) |
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
{zip, find} = require 'underscore' | |
Proxy = require 'node-proxy' | |
{AssertionError} = require 'assert' | |
Matcher = (@pred) -> | |
Matcher::check = (v) -> @pred(v) | |
Matcher::toString = -> "[Matcher #{@pred.toString().replace(/\n\s*\t*/g, ' ')}]" | |
Expectation = (@mock, @thisBinding, @args) -> | |
{expectations} = Proxy.hidden @mock, 'mockState' | |
expectations.push @ | |
Expectation::return = (v) -> @returnValue = v | |
Expectation::then = (cb) -> @callback = cb | |
Expectation::times = (n) -> | |
{expectations} = Proxy.hidden @mock, 'mockState' | |
for [1...n] | |
expectations.push @ | |
return @ | |
Expectation::match = (object, args) -> | |
matchList [@thisBinding, @args...], [object, args...] | |
matchList = (exps, gots) -> | |
return false unless exps.length == gots.length | |
for [expected, got] in zip(exps, gots) | |
return false unless matchOneThing expected, got | |
return true | |
# TODO match deep structures | |
matchOneThing = (exp, got) -> | |
if exp instanceof Matcher | |
exp.check(got) | |
else exp == got | |
recursiveMatch = (exp, got) -> | |
if typeof exp == 'object' and not (exp instanceof Matcher) | |
return false unless 'object' == typeof got | |
for k, v of exp | |
return false unless recursiveMatch exp[k], got[k] | |
return true | |
else | |
matchOneThing exp, got | |
mockHandlerMaker = (obj) -> | |
set: (target, name, value) -> obj[name] = value | |
get: (target, name) -> | |
if name == 'inspect' then return undefined | |
obj[name] ?= exports.mock() | |
BaseMock = Proxy.create mockHandlerMaker {} | |
# PUBLIC API | |
@mock = (obj={}) -> | |
hidden = calls: [], expectations: [] | |
f = Proxy.createFunction mockHandlerMaker(obj), (args...) -> | |
{calls, expectations} = hidden | |
if (exp = expectations[0])? and exp.match @, args | |
expectations.shift() | |
calls.push this: @, args: args, expected: true | |
if exp.returnValue then exp.returnValue | |
else if exp.callback then exp.callback.call @, args... | |
else | |
calls.push this: @, args: args, expected: false | |
return undefined | |
Proxy.hidden f, 'mockState', hidden | |
f.__proto__ = BaseMock | |
return f | |
@isDeep = (expect) -> | |
new Matcher (got) -> recursiveMatch expect, got | |
@pred = (pred) -> new Matcher pred | |
@match = (regex) -> new Matcher (x) -> x.match(regex) | |
@any = new Matcher(-> true) | |
@expect = (mock, args...) -> new Expectation mock, mock, args | |
@expectThis = (mock, _this, args...) -> new Expectation mock, _this, args | |
@lastCall = (mock) -> | |
{calls} = Proxy.hidden mock, 'mockState' | |
i = calls.length | |
return null unless i | |
calls[i-1] | |
@verify = (mock, args...) -> | |
{calls} = Proxy.hidden mock, 'mockState' | |
i = 0 | |
match = find calls, (call) -> | |
i++; matchCall {this: mock, args: args}, call | |
if not match | |
throw new AssertionError "No call to #{mock} matched #{args}" | |
calls.splice(i-1,1) unless match.expected | |
null | |
@verifyNoMore = (mock) -> | |
{calls} = Proxy.hidden mock, 'mockState' | |
unexpected = calls.filter (x) -> not x.expected | |
if unexpected.length | |
throw new AssertionError "Unexpected calls #{unexpected} to #{mock}" | |
matchCall = (exp, got) -> | |
matchList [exp.this, exp.args...], [got.this, got.args...] |
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
{ | |
"author": "Stephen Sugden <stephen@betsmartmedia.com> (www.betsmartmedia.com)", | |
"name": "mtfw", | |
"description": "Mocks that fucking work", | |
"version": "0.1.1", | |
"repository": { | |
"type": "git", | |
"url": "git@gist.github.com:1315749.git" | |
}, | |
"main": "./mtfw.coffee", | |
"scripts": { | |
"test": "vows core.test.coffee" | |
}, | |
"engines": { | |
"node": "~0.4.12" | |
}, | |
"dependencies": { | |
"coffee-script": "~1.1.2", | |
"node-proxy": "~0.5.2", | |
"underscore": "~1.2.1" | |
}, | |
"devDependencies": { | |
"vows": "~0.5.12" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment