Skip to content

Instantly share code, notes, and snippets.

@grncdr
Created October 26, 2011 08:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grncdr/1315749 to your computer and use it in GitHub Desktop.
Save grncdr/1315749 to your computer and use it in GitHub Desktop.
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)
{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...]
{
"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