Any way to modify Jasmine spies based on arguments?
JavascriptUnit TestingJasmineJavascript Problem Overview
I have a function I'd like to test which calls an external API method twice, using different parameters. I'd like to mock this external API out with a Jasmine spy, and return different things based on the parameters. Is there any way to do this in Jasmine? The best I can come up with is a hack using andCallFake:
var functionToTest = function() {
var userName = externalApi.get('abc');
var userId = externalApi.get('123');
};
describe('my fn', function() {
it('gets user name and ID', function() {
spyOn(externalApi, 'get').andCallFake(function(myParam) {
if (myParam == 'abc') {
return 'Jane';
} else if (myParam == '123') {
return 98765;
}
});
});
});
Javascript Solutions
Solution 1 - Javascript
In Jasmine versions 3.0 and above you can use withArgs
describe('my fn', function() {
it('gets user name and ID', function() {
spyOn(externalApi, 'get')
.withArgs('abc').and.returnValue('Jane')
.withArgs('123').and.returnValue(98765);
});
});
For Jasmine versions earlier than 3.0 callFake
is the right way to go, but you can simplify it using an object to hold the return values
describe('my fn', function() {
var params = {
'abc': 'Jane',
'123': 98765
}
it('gets user name and ID', function() {
spyOn(externalApi, 'get').and.callFake(function(myParam) {
return params[myParam]
});
});
});
Depending on the version of Jasmine, the syntax is slightly different:
- 1.3.1:
.andCallFake(fn)
- 2.0:
.and.callFake(fn)
Resources:
Solution 2 - Javascript
You could also use $provide
to create a spy. And mock using and.returnValues
instead of and.returnValue
to pass in parameterised data.
> As per Jasmine docs: By chaining the spy with and.returnValues
, all calls to the function will return specific values in order until it reaches the end of the return values list, at which point it will return undefined for all subsequent calls.
describe('my fn', () => {
beforeEach(module($provide => {
$provide.value('externalApi', jasmine.createSpyObj('externalApi', ['get']));
}));
it('get userName and Id', inject((externalApi) => {
// Given
externalApi.get.and.returnValues('abc','123');
// When
//insert your condition
// Then
// insert the expectation
}));
});
Solution 3 - Javascript
In my case, I had a component I was testing and, in its constructor, there is a config service with a method called getAppConfigValue that is called twice, each time with different arguments:
constructor(private configSvc: ConfigService) {
this.configSvc.getAppConfigValue('a_string');
this.configSvc.getAppConfigValue('another_string');
}
In my spec, I provided the ConfigService in the TestBed like so:
{
provide: ConfigService,
useValue: {
getAppConfigValue: (key: any): any {
if (key === 'a_string) {
return 'a_value';
} else if (key === 'another_string') {
return 'another_value';
}
}
} as ConfigService
}
So, as long as the signature for getAppConfigValue is the same as specified in the actual ConfigService, what the function does internally can be modified.