How do I test that the method has been called in jasmine? - javascript

I'm a bit confused with spying on jasmine. I have a code like this, but I'm not sure how to test it.
var params = {
param1: "",
param2: "link",
param3: "1",
param4 : "1"
};
var func = new myFunction(params );
func.doSomething();
How do test that func.doSomething has been called.
This is the test I have written so far
describe("Library", function() {
beforeEach(function() {
});
it("should include correct parameters", function() {
expect(params.param1).toEqual("");
expect(params.param2).toEqual("link");
expect(params.param3).toEqual("1");
expect(params.param4).toEqual("1");
});
it("should show that method doSomething is called with zero arguments", function() {
// I'm not sure how to write test for this.
});
});

I think you want to use toHaveBeenCalledWith():
it("should show that method doSomething is called with zero arguments", function() {
// Ensure the spy was called with the correct number of arguments
// In this case, no arguments
expect(func.doSomething).toHaveBeenCalledWith();
});

If spy function has been called exactly once, use toHaveBeenCalledOnceWith matcher:
expect(mySpy).toHaveBeenCalledOnceWith('', 'link', "1", "1");
It combines toHaveBeenCalledTimes(1) and toHaveBeenCalledWith() matchers.
Coming with Jasmine 3.6.

Related

Pass a Jasmine spec on async timeout

Running Jasmine 2.8.
I've got a test case in which the failure case is an event handler being triggered under a condition when it should not be. Note that the codebase that provides events here is part of a proprietary, developed-in-house system and as such these are not DOM events and I am not leveraging any popular JS frameworks:
it('should trigger the event handler in state 0', function (done) {
function specCb(ev) {
expect(ev).toBeDefined();
expect(ev.data).toBe('some value');
done();
}
thing.state = 0;
simulateEventTrigger(thing, specCb);
});
it('should not trigger the event handler in state 1', function (done) {
function specCb(ev) {
done.fail('this should not be called in state 1');
}
thing.state = 1;
simulateEventTrigger(thing, specCb);
});
The second spec will always fail because either the callback is called, which explicitly fails the spec, or the spec times out waiting for done() to be called, thus failing it. How do I get Jasmine to pass a spec if it times out?
Check out spies in Jasmine. Spies allow you to "spy on" a function and assert whether it has been called and with which arguments. Or, in your case, whether it has not been called. An example might look like this...
describe("A spy", function() {
var evHandlers;
beforeEach(function() {
evHandlers = {
callback: function (e) {}
}
spyOn(evHandlers, 'callback');
});
it('should not trigger the event handler in state 1', function (done) {
thing.state = 1;
simulateEventTrigger(thing, evHandlers.callback);
expect(evHandlers.callback).not.toHaveBeenCalled();
});
});
Actually it function accepts the callback so you could do something like this:
const TEST_TIMEOUT = 1000;
const CONSIDER_PASSED_AFTER = 500;
describe('a test that is being considered passing in case of a timeout', () => {
it('should succeed in after a specified time interval', done => {
setTimeout(() => {
done();
}, CONSIDER_PASSED_AFTER);
}, TEST_TIMEOUT);
});

mock a function call using jasmine

Getting started with HotTowelAngular template and I'm setting up unit testing. Ran into a snag.
I'm trying to test a function in my controller that happens to call another function named "log". This "log" is a function stored in a private variable which gets its value from a dependency called "common".
I'm aware that I probably need to stub this function in some way, but I'm unsure of where to start for this particular example, as I'm pretty new to angularjs, jasmine, et all. Any ideas are appreciated
unit test:
describe("quote", function () {
var scope,
controller,
common;
beforeEach(inject(function($rootScope, $controller, _common_) {
scope = $rootScope.$new();
common = _common_;
controller = $controller;
}));
describe("removeAttachment", function() {
it("should remove the attachment when requested", function() {
var vm = controller("quote", { $scope: scope });
vm.attachmentList.push({ FileName: "file1", FileAsString: "..." });
vm.attachmentList.push({ FileName: "file2", FileAsString: "..." });
vm.attachmentList.push({ FileName: "file3", FileAsString: "..." });
expect(vm.attachmentList.length).toEqual(3);
// here's the call where it fails
vm.removeAttachment(vm.attachmentList[1]);
expect(vm.attachmentListCount).toEqual(2);
expect(vm.attachmentList.length).toEqual(2);
expect(vm.attachmentList[1].FileName).toBe("file3");
});
});
});
controller:
var getLogFn = common.logger.getLogFn;
var log = getLogFn(controllerId);
function removeAttachment(attachment) {
// need to stub this out
log('removing attachment: ' + attachment.FileName);
vm.attachmentList.splice(vm.attachmentList.indexOf(attachment), 1);
vm.attachmentListCount = vm.attachmentList.length;
}
The error from Jasmine:
TypeError: 'undefined' is not a function (evaluating 'log('removing attachment: ' + attachment.FileName)')
In your test you should have controller = $contoller("YourController", {it's dependencies});
You probably don't want to pass in your common service, but create a stub that returns a function.
var wrapper = {logger: function () {}};
var stub = { logger: { getLogFun: function() {return wrapper.logger} }};
You can pass that in place of your common service.
You can now spy on it with:
spyOn(wrapper, 'logger');
Is var log = getLogFn(controllerId) being called before removeAttachment? Is getLogFn() injected into the controller? If these are both true, you could stub out getLogFn() to return a dummy log object that you could use for testing. I am not aware of hottowel, I use Sinon. In Sinon, I would call
var getLogFnStub = sinon.stub().returns(function (msg) { return 1;/*My expected result*/ });
and pass that into your controller, like
var vm = controller("quote", { $scope: scope, getLogFn: getLogFnStub});
If you wanted, you could then do asserts against the stub you made in Sinon, like so
sinon.assert.called(getLogFnStub);

jasmine: Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL

I have an angular service called requestNotificationChannel:
app.factory("requestNotificationChannel", function($rootScope) {
var _DELETE_MESSAGE_ = "_DELETE_MESSAGE_";
function deleteMessage(id, index) {
$rootScope.$broadcast(_DELETE_MESSAGE_, { id: id, index: index });
};
return {
deleteMessage: deleteMessage
};
});
I am trying to unit test this service using jasmine:
"use strict";
describe("Request Notification Channel", function() {
var requestNotificationChannel, rootScope, scope;
beforeEach(function(_requestNotificationChannel_) {
module("messageAppModule");
inject(function($injector, _requestNotificationChannel_) {
rootScope = $injector.get("$rootScope");
scope = rootScope.$new();
requestNotificationChannel = _requestNotificationChannel_;
})
spyOn(rootScope, '$broadcast');
});
it("should broadcast delete message notification", function(done) {
requestNotificationChannel.deleteMessage(1, 4);
expect(rootScope.$broadcast).toHaveBeenCalledWith("_DELETE_MESSAGE_", { id: 1, index: 4 });
done();
});
});
I read about the Asynchronous Support in Jasmine, but as I am rather new to unit testing with javascript couldn't make it work.
I am receiving an error :
Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL
and my test is taking too long to execute (about 5s).
Can somebody help me providing working example of my code with some explanation?
Having an argument in your it function (done in the code below) will cause Jasmine to attempt an async call.
//this block signature will trigger async behavior.
it("should work", function(done){
//...
});
//this block signature will run synchronously
it("should work", function(){
//...
});
It doesn't make a difference what the done argument is named, its existence is all that matters. I ran into this issue from too much copy/pasta.
The Jasmine Asynchronous Support docs note that argument (named done above) is a callback that can be called to let Jasmine know when an asynchronous function is complete. If you never call it, Jasmine will never know your test is done and will eventually timeout.
Even for async tests, there is a timeout that goes off in this cases, You can work around this error by increasing the value for the limit timeout to evaluate an async Jasmine callback
describe('Helper', function () {
var originalTimeout;
beforeEach(function() {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000;
});
afterEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
it('Template advance', function(doneFn) {
$.ajax({
url: 'public/your-end-point.mock.json',
dataType: 'json',
success: function (data, response) {
// Here your expected using data
expect(1).toBe(1)
doneFn();
},
error: function (data, response) {
// Here your expected using data
expect(1).toBe(1)
doneFn();
}
});
});
});
Source: http://jasmine.github.io/2.0/introduction.html#section-42
This error can also be caused by leaving out inject when initializing a service/factory or whatever. For example, it can be thrown by doing this:
var service;
beforeEach(function(_TestService_) {
service = _TestService_;
});
To fix it just wrap the function with inject to properly retrieve the service:
var service;
beforeEach(inject(function(_TestService_) {
service = _TestService_;
}));
import { fakeAsync, ComponentFixture, TestBed } from '#angular/core/testing';
use fakeAsync
beforeEach(fakeAsync (() => {
//your code
}));
describe('Intilalize', () => {
it('should have a defined component', fakeAsync(() => {
createComponent();
expect(_AddComponent.ngOnInit).toBeDefined();
}));
});
You can use karma-jasmine plugin to set the default time out interval globally.
Add this config in karma.conf.js
module.exports = function(config) {
config.set({
client: {
jasmine: {
timeoutInterval: 10000
}
}
})
}
This error started out of the blue for me, on a test that had always worked. I couldn't find any suggestions that helped until I noticed my Macbook was running sluggishly. I noticed the CPU was pegged by another process, which I killed. The Jasmine async error disappeared and my tests are fine once again.
Don't ask me why, I don't know. But in my circumstance it seemed to be a lack of system resources at fault.
This is more of an observation than an answer, but it may help others who were as frustrated as I was.
I kept getting this error from two tests in my suite. I thought I had simply broken the tests with the refactoring I was doing, so after backing out changes didn't work, I reverted to earlier code, twice (two revisions back) thinking it'd get rid of the error. Doing so changed nothing. I chased my tail all day yesterday, and part of this morning without resolving the issue.
I got frustrated and checked out the code onto a laptop this morning. Ran the entire test suite (about 180 tests), no errors. So the errors were never in the code or tests. Went back to my dev box and rebooted it to clear anything in memory that might have been causing the issue. No change, same errors on the same two tests. So I deleted the directory from my machine, and checked it back out. Voila! No errors.
No idea what caused it, or how to fix it, but deleting the working directory and checking it back out fixed whatever it was.
Hope this helps someone.
You also get this error when expecting something in the beforeAll function!
describe('...', function () {
beforeAll(function () {
...
expect(element(by.css('[id="title"]')).isDisplayed()).toBe(true);
});
it('should successfully ...', function () {
}
}
Don't use done, just leave the function call empty.
It looks like the test is waiting for some callback that never comes. It's likely because the test is not executed with asynchronous behavior.
First, see if just using fakeAsync in your "it" scenario:
it('should do something', fakeAsync(() => {
You can also use flush() to wait for the microTask queue to finish or tick() to wait a specified amount of time.
In my case, this error was caused by improper use of "fixture.detectChanges()" It seems this method is an event listener (async) which will only respond a callback when changes are detected. If no changes are detected it will not invoke the callback, resulting in a timeout error. Hope this helps :)
Works after removing the scope reference and the function arguments:
"use strict";
describe("Request Notification Channel", function() {
var requestNotificationChannel, rootScope;
beforeEach(function() {
module("messageAppModule");
inject(function($injector, _requestNotificationChannel_) {
rootScope = $injector.get("$rootScope");
requestNotificationChannel = _requestNotificationChannel_;
})
spyOn(rootScope, "$broadcast");
});
it("should broadcast delete message notification with provided params", function() {
requestNotificationChannel.deleteMessage(1, 4);
expect(rootScope.$broadcast).toHaveBeenCalledWith("_DELETE_MESSAGE_", { id: 1, index: 4} );
});
});
What I did was: Added/Updated the following code:
framework: 'jasmine',
jasmineNodeOpts:
{
// Jasmine default timeout
defaultTimeoutInterval: 60000,
expectationResultHandler(passed, assertion)
{
// do something
},
}
As noted by #mastablasta, but also to add that if you call the 'done' argument or rather name it completed you just call the callback completed() in your test when it's done.
// this block signature will trigger async behavior.
it("should work", function(done){
// do stuff and then call done...
done();
});
// this block signature will run synchronously
it("should work", function(){
//...
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
Keeping this in the block solved my issue.
it('', () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
});
Instead of
beforeEach(() => {..
use
beforeEach(fakeAsync(() => {..
In my case, a timeout was cause because of a failed injection of a service with providedIn: 'root'. It's not clear why injection failed, nor why there was no early error if there is apparently no instance of provider available.
I was able to work around it by manually providing a value:
TestBed.configureTestingModule({
declarations: [
// ...
],
imports: [
// ...
],
providers: [
// ...
{ provide: MyService, useValue: { /* ... */ } },
]
}).compileComponents();
I have caught the same error because I used the setTimeout function in the component. Example:
ngOnInit(): void {
this.changeState();
}
private changeState(): void {
setTimeout(() => this.state = StateEnum.IN_PROGRESS, 10000);
}
When I changed the timeout from 10000ms to 0 or less than 5000ms (DEFAULT_TIMEOUT_INTERVAL), all tests were passed.
In my case, I was not returning the value from the spy method, hence facing error,
mainMethod(args): Observable<something>{
return nestedMethod().pipe();
}
Your Test should like below,
it('your test case', (done: DoneFn) => {
const testData = {}; // Your data
spyOn(service, 'nestedMethod').and.returnValue(of(testData));
const obxValue = service.mainMethod('your args');
obxValue.pipe(first()).subscribe((data) => {
expect(data).not.toBeUndefined();
done();
});
});
If you have an argument (done) in the it function try to remove it as well it's call within the function itself:
it("should broadcast delete message notification", function(/*done -> YOU SHOULD REMOVE IT */) {
requestNotificationChannel.deleteMessage(1, 4);
expect(rootScope.$broadcast).toHaveBeenCalledWith("_DELETE_MESSAGE_", { id: 1, index: 4 });
// done(); -> YOU SHOULD REMOVE IT
});

Jasmine testing multiple spies

I'm writing a few tests for an Angular application, these are my first stab at unit tests for Angular using Jasmine. I'm having trouble structuring the test to cater for the various scenarios inside the function (namely the if statement and callbacks).
Here's my $scope function, which takes an Object as an argument, and if that object has an id, then it updates the object (as it'll already exist), otherwise it creates a new report and pushes to the backend using the CRUD service.
$scope.saveReport = function (report) {
if (report.id) {
CRUD.update(report, function (data) {
Notify.success($scope, 'Report updated!');
});
} else {
CRUD.create(report, function (data) {
$scope.report = data;
Notify.success($scope, 'Report successfully created!');
});
}
};
My test so far passes in a fake Object with an id so it'll trigger the CRUD.update method, which I then check is called.
describe('$scope.saveReport', function () {
var reports, testReport;
beforeEach(function () {
testReport = {
"id": "123456789",
"name": "test"
};
spyOn(CRUD, 'update');
$scope.saveReport(testReport);
});
it('should call CRUD factory and update', function () {
expect(CRUD.update).toHaveBeenCalledWith(testReport, jasmine.any(Function));
});
});
I understand Jasmine doesn't allow multiple spies, but I want to be able to somehow test for the if condition, and run a mock test for when the Object doesn't pass in an Object too:
describe('$scope.saveReport', function () {
var reports, testReport;
beforeEach(function () {
testReport = {
"id": "123456789",
"name": "test"
};
testReportNoId = {
"name": "test"
};
spyOn(CRUD, 'update');
spyOn(CRUD, 'create'); // TEST FOR CREATE (NoId)
spyOn(Notify, 'success');
$scope.saveReport(testReport);
$scope.saveReport(testReportNoId); // TEST FOR NO ID
});
it('should call CRUD factory and update', function () {
expect(CRUD.update).toHaveBeenCalledWith(testReport, jasmine.any(Function));
// UNSURE ON THIS PART TOO
});
});
I've read things about using the .andCallFake() method, but I could not see how this could work with my setup. Any help really appreciated.
It seems that you should decide on what you need to test first. If you want to test simply that update is called when id exists or create is called when it does not then you should just structure the it function with those conditions. The before each is the wrong place for some of those things.
it('should call CRUD factory and update', function () {
spyOn(CRUD, 'update');
$scope.saveReport(testReport);
expect(CRUD.update).toHaveBeenCalledWith(testReport, jasmine.any(Function));
});
it('should call CRUD create', function() {
spyOn(CRUD, 'create');
$scope.saveReport(testReportNoId); // TEST FOR NO ID
expect(CRUD.create).toHaveBeenCalledWith(testReport, jasmine.any(Function));
});
Only put things in the before each that you actually should do before each test.
Hope this helped!

webdriverjs: Expected string got an object

After discovering Mocha and webdriverjs, I wanted to give it a shot, after reading the readme.md in https://github.com/camme/webdriverjs, so i began with a trivial test.
var webdriverjs = require("webdriverjs"),
client = webdriverjs.remote(),
expect = require("chai").expect;
suite('Functional tests', function(done) {
setup(function(done) {
client.init().url('http://www.google.com', done);
});
test('test if you can open a firefox page', function() {
var inputType = client.getAttribute('#searchtext_home', 'type');
expect(inputType).to.be.a('string');
console.log(myString);
});
teardown(function(done) {
client.end(done);
//done();
});
});
Get the input element of google and expect its type is text.
I end up with an object in inputType variable.
AssertionError: expected { Object (sessionId, desiredCapabilities, ...) } to be a string
It does return an object from client.getAttribute(). So you should use it's 3rd parameter which is a callback function like this:
test('test if you can open a firefox page', function(done) {
client.getAttribute('#searchtext_home', 'type', function(err, inputType) {
expect(inputType).to.be.a('string');
done();
});
});
See more detail example code here.

Categories

Resources