The old style of merging all of a function's variable declarations into a single statement made some sense back in the days of var, but there's no reason to keep doing it now that we use const and let.
299 lines
9.4 KiB
JavaScript
299 lines
9.4 KiB
JavaScript
describe('Spies', function() {
|
|
let env;
|
|
|
|
beforeEach(function() {
|
|
env = new privateUnderTest.Env();
|
|
});
|
|
|
|
afterEach(function() {
|
|
env.cleanup_();
|
|
});
|
|
|
|
describe('createSpy', function() {
|
|
let TestClass;
|
|
|
|
beforeEach(function() {
|
|
TestClass = function() {};
|
|
TestClass.prototype.someFunction = function() {};
|
|
TestClass.prototype.someFunction.bob = 'test';
|
|
});
|
|
|
|
it('preserves the properties of the spied function', function() {
|
|
const spy = env.createSpy(
|
|
TestClass.prototype,
|
|
TestClass.prototype.someFunction
|
|
);
|
|
|
|
expect(spy.bob).toEqual('test');
|
|
});
|
|
|
|
it('should allow you to omit the name argument and only pass the originalFn argument', function() {
|
|
const fn = function test() {};
|
|
const spy = env.createSpy(fn);
|
|
|
|
expect(spy.and.identity).toEqual('test');
|
|
});
|
|
|
|
it('warns the user that we intend to overwrite an existing property', function() {
|
|
TestClass.prototype.someFunction.and = 'turkey';
|
|
|
|
expect(function() {
|
|
env.createSpy(TestClass.prototype, TestClass.prototype.someFunction);
|
|
}).toThrowError(
|
|
"Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon"
|
|
);
|
|
});
|
|
|
|
it('adds a spyStrategy and callTracker to the spy', function() {
|
|
const spy = env.createSpy(
|
|
TestClass.prototype,
|
|
TestClass.prototype.someFunction
|
|
);
|
|
|
|
expect(spy.and).toEqual(jasmine.any(privateUnderTest.SpyStrategy));
|
|
expect(spy.calls).toEqual(jasmine.any(privateUnderTest.CallTracker));
|
|
});
|
|
|
|
it('tracks the argument of calls', function() {
|
|
const spy = env.createSpy(
|
|
TestClass.prototype,
|
|
TestClass.prototype.someFunction
|
|
);
|
|
const trackSpy = spyOn(spy.calls, 'track');
|
|
|
|
spy('arg');
|
|
|
|
expect(trackSpy.calls.mostRecent().args[0].args).toEqual(['arg']);
|
|
});
|
|
|
|
it('tracks the context of calls', function() {
|
|
const spy = env.createSpy(
|
|
TestClass.prototype,
|
|
TestClass.prototype.someFunction
|
|
);
|
|
const trackSpy = spyOn(spy.calls, 'track');
|
|
|
|
const contextObject = { spyMethod: spy };
|
|
contextObject.spyMethod();
|
|
|
|
expect(trackSpy.calls.mostRecent().args[0].object).toEqual(contextObject);
|
|
});
|
|
|
|
it('tracks the return value of calls', function() {
|
|
const spy = env.createSpy(
|
|
TestClass.prototype,
|
|
TestClass.prototype.someFunction
|
|
);
|
|
const trackSpy = spyOn(spy.calls, 'track');
|
|
|
|
spy.and.returnValue('return value');
|
|
spy();
|
|
|
|
expect(trackSpy.calls.mostRecent().args[0].returnValue).toEqual(
|
|
'return value'
|
|
);
|
|
});
|
|
|
|
it('preserves arity of original function', function() {
|
|
const functions = [
|
|
function nullary() {},
|
|
function unary(arg) {},
|
|
function binary(arg1, arg2) {},
|
|
function ternary(arg1, arg2, arg3) {},
|
|
function quaternary(arg1, arg2, arg3, arg4) {},
|
|
function quinary(arg1, arg2, arg3, arg4, arg5) {},
|
|
function senary(arg1, arg2, arg3, arg4, arg5, arg6) {}
|
|
];
|
|
|
|
for (let arity = 0; arity < functions.length; arity++) {
|
|
const someFunction = functions[arity];
|
|
const spy = env.createSpy(someFunction.name, someFunction);
|
|
|
|
expect(spy.length).toEqual(arity);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('createSpyObj', function() {
|
|
it('should create an object with spy methods and corresponding return values when you call jasmine.createSpyObj() with an object', function() {
|
|
const spyObj = env.createSpyObj('BaseName', {
|
|
method1: 42,
|
|
method2: 'special sauce'
|
|
});
|
|
|
|
expect(spyObj.method1()).toEqual(42);
|
|
expect(spyObj.method1.and.identity).toEqual('BaseName.method1');
|
|
|
|
expect(spyObj.method2()).toEqual('special sauce');
|
|
expect(spyObj.method2.and.identity).toEqual('BaseName.method2');
|
|
});
|
|
|
|
it('should create an object with a bunch of spy methods when you call jasmine.createSpyObj()', function() {
|
|
const spyObj = env.createSpyObj('BaseName', ['method1', 'method2']);
|
|
|
|
expect(spyObj).toEqual({
|
|
method1: jasmine.any(Function),
|
|
method2: jasmine.any(Function)
|
|
});
|
|
expect(spyObj.method1.and.identity).toEqual('BaseName.method1');
|
|
expect(spyObj.method2.and.identity).toEqual('BaseName.method2');
|
|
});
|
|
|
|
it('should allow you to omit the baseName', function() {
|
|
const spyObj = env.createSpyObj(['method1', 'method2']);
|
|
|
|
expect(spyObj).toEqual({
|
|
method1: jasmine.any(Function),
|
|
method2: jasmine.any(Function)
|
|
});
|
|
expect(spyObj.method1.and.identity).toEqual('unknown.method1');
|
|
expect(spyObj.method2.and.identity).toEqual('unknown.method2');
|
|
});
|
|
|
|
it('should throw if you do not pass an array or object argument', function() {
|
|
expect(function() {
|
|
env.createSpyObj('BaseName');
|
|
}).toThrowError(
|
|
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
|
);
|
|
});
|
|
|
|
it('should throw if you pass an empty array argument', function() {
|
|
expect(function() {
|
|
env.createSpyObj('BaseName', []);
|
|
}).toThrowError(
|
|
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
|
);
|
|
});
|
|
|
|
it('should throw if you pass an empty object argument', function() {
|
|
expect(function() {
|
|
env.createSpyObj('BaseName', {});
|
|
}).toThrowError(
|
|
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
|
);
|
|
});
|
|
|
|
it('creates an object with spy properties if a second list is passed', function() {
|
|
const spyObj = env.createSpyObj('base', ['method1'], ['prop1']);
|
|
|
|
expect(spyObj).toEqual({
|
|
method1: jasmine.any(Function),
|
|
prop1: undefined
|
|
});
|
|
|
|
const descriptor = Object.getOwnPropertyDescriptor(spyObj, 'prop1');
|
|
expect(descriptor.get.and.identity).toEqual('base.prop1.get');
|
|
expect(descriptor.set.and.identity).toEqual('base.prop1.set');
|
|
});
|
|
|
|
it('creates an object with property names and return values if second object is passed', function() {
|
|
const spyObj = env.createSpyObj('base', ['method1'], {
|
|
prop1: 'foo',
|
|
prop2: 37
|
|
});
|
|
|
|
expect(spyObj).toEqual({
|
|
method1: jasmine.any(Function),
|
|
prop1: 'foo',
|
|
prop2: 37
|
|
});
|
|
|
|
expect(spyObj.prop1).toEqual('foo');
|
|
expect(spyObj.prop2).toEqual(37);
|
|
spyObj.prop2 = 4;
|
|
expect(spyObj.prop2).toEqual(37);
|
|
expect(
|
|
Object.getOwnPropertyDescriptor(spyObj, 'prop2').set.calls.count()
|
|
).toBe(1);
|
|
});
|
|
|
|
it('allows base name to be omitted when assigning methods and properties', function() {
|
|
const spyObj = env.createSpyObj({ m: 3 }, { p: 4 });
|
|
|
|
expect(spyObj.m()).toEqual(3);
|
|
expect(spyObj.p).toEqual(4);
|
|
expect(
|
|
Object.getOwnPropertyDescriptor(spyObj, 'p').get.and.identity
|
|
).toEqual('unknown.p.get');
|
|
});
|
|
});
|
|
|
|
it('can use different strategies for different arguments', function() {
|
|
const spy = env.createSpy('foo');
|
|
spy.and.returnValue(42);
|
|
spy.withArgs('baz', 'grault').and.returnValue(-1);
|
|
spy.withArgs('thud').and.returnValue('bob');
|
|
|
|
expect(spy('foo')).toEqual(42);
|
|
expect(spy('baz', 'grault')).toEqual(-1);
|
|
expect(spy('thud')).toEqual('bob');
|
|
expect(spy('baz', 'grault', 'waldo')).toEqual(42);
|
|
});
|
|
|
|
it('uses asymmetric equality testers when selecting a strategy', function() {
|
|
const spy = env.createSpy('foo');
|
|
spy.and.returnValue(42);
|
|
spy.withArgs(jasmineUnderTest.any(String)).and.returnValue(-1);
|
|
|
|
expect(spy('foo')).toEqual(-1);
|
|
expect(spy({})).toEqual(42);
|
|
});
|
|
|
|
it('uses the provided matchersUtil selecting a strategy', function() {
|
|
const matchersUtil = new privateUnderTest.MatchersUtil({
|
|
customTesters: [
|
|
function(a, b) {
|
|
if ((a === 'bar' && b === 'baz') || (a === 'baz' && b === 'bar')) {
|
|
return true;
|
|
}
|
|
}
|
|
]
|
|
});
|
|
const spy = new privateUnderTest.Spy('aSpy', matchersUtil);
|
|
spy.and.returnValue('default strategy return value');
|
|
spy.withArgs('bar').and.returnValue('custom strategy return value');
|
|
expect(spy('foo')).toEqual('default strategy return value');
|
|
expect(spy('baz')).toEqual('custom strategy return value');
|
|
});
|
|
|
|
it('can reconfigure an argument-specific strategy', function() {
|
|
const spy = env.createSpy('foo');
|
|
spy.withArgs('foo').and.returnValue(42);
|
|
spy.withArgs('foo').and.returnValue(17);
|
|
expect(spy('foo')).toEqual(17);
|
|
});
|
|
|
|
describe('any promise-based strategy', function() {
|
|
it('works with global Promise library', function(done) {
|
|
const spy = env.createSpy('foo').and.resolveTo(42);
|
|
spy()
|
|
.then(function(result) {
|
|
expect(result).toEqual(42);
|
|
done();
|
|
})
|
|
.catch(done.fail);
|
|
});
|
|
});
|
|
|
|
describe('when withArgs is used without a base strategy', function() {
|
|
it('uses the matching strategy', function() {
|
|
const spy = env.createSpy('foo');
|
|
spy.withArgs('baz').and.returnValue(-1);
|
|
|
|
expect(spy('baz')).toEqual(-1);
|
|
});
|
|
|
|
it("throws if the args don't match", function() {
|
|
const spy = env.createSpy('foo');
|
|
spy.withArgs('bar').and.returnValue(-1);
|
|
|
|
expect(function() {
|
|
spy('baz', { qux: 42 });
|
|
}).toThrowError(
|
|
"Spy 'foo' received a call with arguments [ 'baz', Object({ qux: 42 }) ] but all configured strategies specify other arguments."
|
|
);
|
|
});
|
|
});
|
|
});
|