function assertSpies(_msg, _exps) {
        var msg = function(i, exp, extra) {
            return [_msg, 'Item #' + i, '@method ' + exp[1], (extra || '')].join(' | ');
        };

        // check `_exps` structure and if "did (not) get called"
        var failEarly = false;
        _exps.forEach(function(exp, i) {
            var spy = exp[0];
            var methodName = exp[1];
            var val = exp[2];
            var calls = spy[methodName].calls;
            var didGetCalled = calls.count() > 0;
            var expectingACall;

            if(Array.isArray(val)) {
                expectingACall = true;
            } else if(val === 0 || val === 1) {
                expectingACall = Boolean(val);
            } else {
                fail(_msg + '- Wrong arguments for assertSpies');
            }

            expect(didGetCalled).toBe(expectingACall, msg(i, exp, 'did (not) get called'));
            failEarly = didGetCalled !== expectingACall;
        });

        if(failEarly) {
            return fail(_msg + '- Wrong calls, assertSpies early fail');
        }

        // filter out `exps` items with `value=0`
        var exps = _exps.filter(function(exp) {
            var val = exp[2];
            return val !== 0;
        });

        // find list of spies to assert (N.B. dedupe using `invocationOrder`)
        var actuals = [];
        var seen = {};
        exps.forEach(function(exp) {
            var spy = exp[0];
            var methodName = exp[1];
            var calls = spy[methodName].calls;
            if(calls.count()) {
                calls.all().forEach(function(c) {
                    var k = c.invocationOrder;
                    if(!seen[k]) {
                        actuals.push([spy, methodName, c.args, k]);
                        seen[k] = 1;
                    }
                });
            }
        });
        actuals.sort(function(a, b) { return a[3] - b[3]; });

        // sanity check
        if(actuals.length !== exps.length) {
            return fail(_msg + '- Something went wrong when building "actual" callData list');
        }

        for(var i = 0; i < exps.length; i++) {
            var exp = exps[i];
            var actual = actuals[i];
            var val = exp[2];
            var args = actual[2];

            if(actual[0] !== exp[0] || actual[1] !== exp[1]) {
                fail(_msg + '- Item #' + i + ' with method "' + exp[1] + '" is out-of-order');
                continue;
            }

            if(Array.isArray(val)) {
                // assert function arguments
                expect(args.length).toBe(val.length, msg(i, exp, '# of args'));

                for(var j = 0; j < args.length; j++) {
                    var arg = args[j];
                    var e = val[j];
                    var msgj = msg(i, exp, 'arg #' + j);

                    if(Lib.isPlainObject(arg)) {
                        expect(arg).withContext(msgj + ' (obj check)').toEqual(e);
                    } else if(Array.isArray(arg)) {
                        expect(arg).withContext(msgj + ' (arr check)').toEqual(e);
                    } else if(typeof arg === 'function') {
                        expect('function').toBe(e, msgj + ' (fn check)');
                    } else {
                        expect(arg).toBe(e, msgj);
                    }
                }
            }
        }

        resetSpyCounters();
    }
assertSpies

@param {string} _msg : base message string
@param {array of arrays} _exps : expectations

 assertSpies('test', [
     [<spied-on {object}>, <method name {string}>, `value`].
     [<spied-on {object}>, <method name {string}>, <args {array}>].
     ....
     [<spied-on {object}>, <method name {string}>, <0 or 1 {number}>]
 ]);

 - expectations items must be placed in the order in which they are invoked
 - to test that a certain spy didn't get called use `0` as value
 - to test that a certain spy did get called w/o checking its args use `1` as value