Does Jasmine's spyOn() method allow the spied on function to be executed, or does it, kind of - intercept the invocation when the spied method is (about to get) invoked, and returns true.
PS: Could anyone point me to the explanation of spyOn()'s inner workings?
Spy :
A spy can pretend to be a function or an object that you can use while writing unit test code to examine behavior of functions/objects
var Person = function() {};
Dictionary.prototype.FirstName = function() {
return "My FirstName";
};
Dictionary.prototype.LastName = function() {
return "My LastName";
};
Person.prototype.MyName = function() {
return FirstName() + " " + LastName();
};
Person.prototype.MyLocation = function() {
Return ”some location”;
};
describe("Person", function() {
it('uses First Name and Last Name for MyName', function() {
var person = new Person;
spyOn(person , "FirstName");
spyOn(person, "LastName");
person.MyName();
expect(person.FirstName).toHaveBeenCalled();
expect(person.LastName).toHaveBeenCalled();
});
});
Through SpyOn you can know whether some function has been / has not been called
expect(person. MyLocation).not.toHaveBeenCalled();
You can ensure that a spy always returns a given value and test it
spyOn(person, " MyName ").andReturn("My FirstNameMy LasttName ");
var result = person.MyName();
expect(result).toEqual("My FirstName My LasttName ");
Spies can call through to a fake function
it("can call a fake function", function() {
var fakeFun = function() {
alert("I am a spy!”);
return "hello";
};
var person = new person();
spyOn(person, "MyName").andCallFake(fakeFun);
person. MyName (); // alert
})
You can even create a NEW spy function or object and make use of it
it("can have a spy function", function() {
var person = new Person();
person.StreetAddress = jasmine.createSpy("Some Address");
person. StreetAddress ();
expect(person. StreetAddress).toHaveBeenCalled();
});
It just creates a mock(spy) object and injects it to your tested code.
It has three main purposes:
Testing your code if it calls spy: toHaveBeenCalled
Testing your code if it calls with appropriate parameters: toHaveBeenCalledWith
Testing your code for different return values from spy: and.callThrough
Related
I am using Firebase Phone Auth in my Flutter project and want to test my auth class. I know how to use when() and .thenAnswer() from Mockito with typical Futures.
I want to test my authentication method, in particular, verificationFailed and verificationCompleted callbacks.
Future<void> getSmsCodeWithFirebase() async {
try {
await _firebaseAuth.verifyPhoneNumber(
phoneNumber: fullPhoneNumber,
timeout: const Duration(seconds: 30),
verificationCompleted: (credential) async {
_firebaseSignIn(credential);
},
verificationFailed: (e) {
errorMessage = 'Error code: ${e.code}';
}
initModelState = DataState.error;
},
codeSent: (String verificationId, int resendToken) {
_firebaseSessionId = verificationId;
initModelState = DataState.idle;
},
codeAutoRetrievalTimeout: (String verificationId) {},
);
} catch (ex) {
errorMessage = 'There was some error';
updateModelState = DataState.error;
}
}
For now I came up with something like this, but I don't understand how to invoke passed callbacks.
test('cant verify phonenumber', () async {
when(mockFirebaseAuth.verifyPhoneNumber(
phoneNumber: any,
codeSent: anyNamed('codeSent'),
verificationCompleted: anyNamed('verificationCompleted'),
verificationFailed: anyNamed('verificationFailed'),
codeAutoRetrievalTimeout: anyNamed('codeAutoRetrievalTimeout')))
.thenAnswer((Invocation invocation) {
// I need to put something here?
});
await authCodeViewModel.getSmsCodeWithFirebase();
expect(authCodeViewModel.initModelState, DataState.error);
});
You're not really asking how to stub callbacks themselves; you're asking how to invoke callbacks for a stubbed method. You'd use captured callback arguments the same as any other captured arguments:
// Sample class with a method that takes a callback.
abstract class Foo {
void f(String Function(int x) callback, int y);
}
#GenerateMocks([Foo])
void main() {
var mockFoo = MockFoo();
mockFoo.f((x) => '$x', 42);
var captured = verify(mockFoo.f(captureAny, any)).captured;
var f = captured[0] as String Function(int);
print(f(88)); // Prints: 88
}
In your case, I think it'd be something like:
test('cant verify phonenumber', () async {
await authCodeViewModel.getSmsCodeWithFirebase();
var captured = verify(mockFirebaseAuth.verifyPhoneNumber(
phoneNumber: any,
codeSent: anyNamed('codeSent'),
verificationCompleted: anyNamed('verificationCompleted'),
verificationFailed: captureNamed('verificationFailed'),
codeAutoRetrievalTimeout: anyNamed('codeAutoRetrievalTimeout')))
.captured;
var verificationFailed = captured[0] as PhoneVerificationFailed;
verificationFailed(FirebaseAuthException());
expect(authCodeViewModel.initModelState, DataState.error);
});
Of course, if you're supplying the callbacks, you don't need to capture them in the first place; you can just invoke them directly yourself.
I am new to mocha/chai. I am trying to write the unit test case for following scenario. I want to test whether "callchildfunc" and "childfunc2" is called or not upon calling "parent()" function.
I have read the fundamental and what I understood is if you are spying on any function then just put assert on that function to check if that function is called or not. Means in above script file If i will spy on "childfunc2" and "callchildfunc" function then I don't need to call here in test file since it is already called in script file under parent function. Please correct my understanding if I am wrong.
This is my script.js file
// script.js
function childfunc2(){
console.log("child function without return");
}
function callchildfunc(var1, var2){
return var1 + var2;
}
function parent(x){
var getreturnval = callchildfunc(x, 1);
console.log(getreturnval);
childfunc2();
}
This is my test file.
//scenario 1
describe('Test for parent() function ', function(){
it('should make parent call ', function(){
var spy1 = sinon.spy(window, 'callchildfunc');
var spy2 = sinon.spy(window, 'childfunc2');
parent(2);
expect(spy1).to.have.been.called();
expect(spy2).to.have.been.called();
// or
sinon.assert.calledOnce(spy1);
sinon.assert.calledOnce(spy1);
});
});
After running the test always I am getting this error.
AssertError: expected childfunc2 to be called once but was called 0 times
Also If I change the test file and call the spy function then it will work.
var spy1 = sinon.spy(window, 'callchildfunc');
var spy2 = sinon.spy(window, 'childfunc2');
parent(2);
// After addding these below 2 lines.
window.childfunc2();
window.callchildfunc();
Any help?
// Script.js
module.exports= {
childfunc2:function(){
console.log("child function without return");
},
callchildfunc:function(var1, var2){
return var1 + var2;
},
parent:function(x){
var getreturnval = this.callchildfunc(x, 1);
console.log(getreturnval);
this.childfunc2();
}
};
// Test.js
var sinon= require('sinon'), expect=require('chai').expect
var r= require('./functests')
describe('Test for parent() function ', function(){
it('should make parent call ', function(){
var spy1 = sinon.spy(r, 'callchildfunc');
var spy2 = sinon.spy(r, 'childfunc2');
r.parent(2);
// expect(spy1).to.have.been.called();
// expect(spy2).to.have.been.called();
// or
sinon.assert.calledOnce(spy1);
sinon.assert.calledOnce(spy1);
});
});
// Screenshot
What is the/one correct way to test this piece of JavaScript code using, e.g, Mocha/Sinon:
var App = function(endPoint, successCallback) {
var channel = new WebSocket(endPoint);
channel.onopen = function(ev) {
successCallback();
};
};
I'm thinking of something like this:
describe('App', function() {
it('test should create instance and call success', function(done) {
var app = new App('ws://foo.bar:123/', done);
var stub = sinon.stub(app, 'channel');
stub.yield('onopen');
});
});
Apparently, that does not work as channel is not accessible from outside the constructor. How would you test this?
Why not create a factory for Websocket such as:
var myApp = {
createWebsocket: function () {
return new Websocket;
}
};
This would make spying on the myApp.createWebsocket return value channel very easy:
sinon.spy(myApp, 'createWebsocket);
var channel = myApp.createWebsocket.firstCall.returnValue;
var stub = sinon.stub(channel, 'onopen');
stub.yield('onopen');
// Clean up
myApp.createWebsocket.restore();
I have an angular service that is used as a factory function to instatiate many object instances of the type Engine like this:
angular.module('parts.engine', []).factory('Engine', function() {
var Engine = function( settings ) {
this.hp = settings.engine.hp;
this.miles = 0;
};
Engine.prototype.setMiles = function( miles ) { this.miles = miles; }
return Engine;
});
Say I have another angular service, that is also used to create instances of an object like this:
angular.module('car', ['parts.engine']).factory('carCreator', function( Engine ) {
var carCreator = function( settings ) {
var engine = new Engine( settings );
engine.setMiles( settings.engine.miles )
return {
brand: settings.brand;
engine: engine;
}
};
return carCreator;
});
So I now instatiate a new instance of a car object like this:
angular.module('carApp', ['car']).controller('AppCtrl', function( carCreator ) {
var settings = {
brand: 'Ford',
engine: {
hp: 125,
miles: 12000
}
};
var newCar = carCreator(settings);
});
Does anyone have an idea how to test the initialization logic:
var engine = new Engine( settings );
engine.setMiles( settings.engine.miles )
in the carCreator factory? I know I can instantiate an object with the carCreator class and check, if the returned objects engine.miles property is set to the correct value. But I have cases, where checking for this will not be as easy, because the initalization logic and the values returned are much more complex. What I would like to do is test the businesslogic of setMiles in the Engine class, and just setup a SpyOn on Engine and engine.setMiles when testing the carCreator class, but how do I do that, when engine is created in the closure?
You can use $provide to provide a mock version of the Engine service to carCreator. You'll then be able to spy on it and assert that the correct steps have been taken.
One of the trickier bits of this is that you're testing a constructor function, which jasmine mocks doesn't play so nicely with, you need to return a mockInstance and spy on that instance.
var mockEngine, mockEngineInstance, carCreatorService;
beforeEach(function () {
module('car', function ($provide) {
// Define a mock instance
mockEngineInstance = {
setMiles: function () {}
};
// Mock the engine service to give a mocked instance
mockEngine = function () {
return mockEngineInstance;
};
$provide.value('Engine', mockEngine);
});
inject(function (carCreator) {
carCreatorService = carCreator;
});
});
it('should test that miles are set when a car is created',
function () {
// Arrange.
spyOn(mockEngineInstance, 'setMiles');
// Act.
carCreatorService({
engine: {
miles: 100
}
});
// Assert.
expect(mockEngineInstance.setMiles).toHaveBeenCalledWith(100);
}
);
This may be a duplicate but I have looked at a lot of other questions here and they usually miss what I am looking for in some way. They mostly talk about a service they created themselves. That I can do and have done. I am trying to override what angular is injecting with my mock. I thought it would be the same but for some reason when I step through the code it is always the angular $cookieStore and not my mock.
I have very limited experience with jasmine and angularjs. I come from a C# background. I usually write unit tests moq (mocking framework for C#). I am use to seeing something like this
[TestClass]
public PageControllerTests
{
private Mock<ICookieStore> mockCookieStore;
private PageController controller;
[TestInitialize]
public void SetUp()
{
mockCookieStore = new Mock<ICookieStore>();
controller = new PageController(mockCookieStore.Object);
}
[TestMethod]
public void GetsCarsFromCookieStore()
{
// Arrange
mockCookieStore.Setup(cs => cs.Get("cars"))
.Return(0);
// Act
controller.SomeMethod();
// Assert
mockCookieStore.VerifyAll();
}
}
I want mock the $cookieStore service which I use in one of my controllers.
app.controller('PageController', ['$scope', '$cookieStore', function($scope, $cookieStore) {
$scope.cars = $cookieStore.get('cars');
if($scope.cars == 0) {
// Do other logic here
.
}
$scope.foo = function() {
.
.
}
}]);
I want to make sure that the $cookieStore.get method is invoked with a 'garage' argument. I also want to be able to control what it gives back. I want it to give back 0 and then my controller must do some other logic.
Here is my test.
describe('Controller: PageController', function () {
var controller,
scope,
cookieStoreSpy;
beforeEach(function () {
cookieStoreSpy = jasmine.createSpyObj('CookieStore', ['get']);
cookieStoreSpy.get.andReturn(function(key) {
switch (key) {
case 'cars':
return 0;
case 'bikes':
return 1;
case 'garage':
return { cars: 0, bikes: 1 };
}
});
module(function($provide) {
$provide.value('$cookieStore', cookieStoreSpy);
});
module('App');
});
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller;
}));
it('Gets car from cookie', function () {
controller('PageController', { $scope: scope });
expect(cookieStoreSpy.get).toHaveBeenCalledWith('cars');
});
});
This is a solution for the discussion we had in my previous answer.
In my controller I'm using $location.path and $location.search. So to overwrite the $location with my mock I did:
locationMock = jasmine.createSpyObj('location', ['path', 'search']);
locationMock.location = "";
locationMock.path.andCallFake(function(path) {
console.log("### Using location set");
if (typeof path != "undefined") {
console.log("### Setting location: " + path);
this.location = path;
}
return this.location;
});
locationMock.search.andCallFake(function(query) {
console.log("### Using location search mock");
if (typeof query != "undefined") {
console.log("### Setting search location: " + JSON.stringify(query));
this.location = JSON.stringify(query);
}
return this.location;
});
module(function($provide) {
$provide.value('$location', locationMock);
});
I didn't have to inject anything in the $controller. It just worked. Look at the logs:
LOG: '### Using location set'
LOG: '### Setting location: /test'
LOG: '### Using location search mock'
LOG: '### Setting search location: {"limit":"50","q":"ani","tags":[1,2],"category_id":5}'
If you want to check the arguments, spy on the method
// declare the cookieStoreMock globally
var cookieStoreMock;
beforeEach(function() {
cookieStoreMock = {};
cookieStoreMock.get = jasmine.createSpy("cookieStore.get() spy").andCallFake(function(key) {
switch (key) {
case 'cars':
return 0;
case 'bikes':
return 1;
case 'garage':
return {
cars: 0,
bikes: 1
};
}
});
module(function($provide) {
$provide.value('cookieStore', cookieStoreMock);
});
});
And then to test the argument do
expect(searchServiceMock.search).toHaveBeenCalledWith('cars');
Here is an example https://github.com/lucassus/angular-seed/blob/81d820d06e1d00d3bae34b456c0655baa79e51f2/test/unit/controllers/products/index_ctrl_spec.coffee#L3 it's coffeescript code with mocha + sinon.js but the idea is the same.
Basically with the following code snippet you could load a module and substitute its services:
beforeEach(module("myModule", function($provide) {
var stub = xxx; //... create a stub here
$provide.value("myService", stub);
}));
Later in the spec you could inject this stubbed service and do assertions:
it("does something magical", inject(function(myService) {
subject.foo();
expect(myService).toHaveBeenCalledWith("bar");
}));
More details and tips about mocking and testing you could find in this excellent blog post: http://www.yearofmoo.com/2013/09/advanced-testing-and-debugging-in-angularjs.html
Why mock cookieStore when you may use it directly without modification? The code below is a partial unit test for a controller which uses $cookieStore to put and get cookies. If your controller has a method known as "setACookie" that uses $cookieStore.put('cookieName', cookieValue) ... then the test should be able to read the value that was set.
describe('My controller', function() {
var $cookieStore;
describe('MySpecificController', function() {
beforeEach(inject(function(_$httpBackend_, $controller, _$cookieStore_) {
$cookieStore = _$cookieStore_;
// [...] unrelated to cookieStore
}));
it('should be able to reference cookies now', function () {
scope.setACookie();
expect($cookieStore.get('myCookieName')).toBe('setToSomething');
});
});