I am very new to Jasmine. I am intending to use it for with vanilla javascript project. The initial configuration was a breeze but I am receiving object not defined error while using spyOn.
I have downloaded the version 3.4.0 Jasmine Release Page and added the files 'as is' to my project. I then have changed jasmine.json file accordingly and see the all the example tests passing. However when try spyOn on a private object, I am getting undefined error,
if (typeof (DCA) == 'undefined') {
DCA = {
__namespace: true
};
}
DCA.Audit = {
//this function needs to be tested
callAuditLogAction: function (parameters) {
//Get an error saying D365 is not defined
D365.API.ExecuteAction("bu_AuditReadAccess", parameters,
function (result) { },
function (error) {
if (error != undefined && error.message != undefined) {
D365.Utility.alertDialog('An error occurred while trying to execute the Action. The response from server is:\n' + error.message);
}
}
);
}
}
and my spec class
describe('Audit', function(){
var audit;
beforeEach(function(){
audit = DCA.Audit;
})
describe('When calling Audit log function', function(){
beforeEach(function(){
})
it('Should call Execute Action', function(){
var D365 = {
API : {
ExecuteAction : function(){
console.log('called');
}
}
}
// expectation is console log with say hello
spyOn(D365.API, 'ExecuteAction').and.callFake(() => console.log('hello'));
var params = audit.constructActionParameters("logicalName", "someId", 'someId');
audit.callAuditLogAction(params);
})
})
})
As you can see my spec class does not know about actual D365 object. I was hoping to stub the D365 object without having to inject it. Do I need to stub out whole 365 library and link it to my test runner html?
I got it working after some pondering. So the library containing D365 should still need to be added to my test runner html file. after that I can fake the method call like below,
it('Should call Execute Action', function(){
spyOn(D365.API, 'ExecuteAction').and.callFake(() => console.log('hello'));
var params = audit.constructActionParameters("logicalName", "someId", 'someId');
audit.callAuditLogAction(params);
})
it is now working.
(I asked this question recently and accepted an answer but it's still not what I need.) I really need to create dynamic tests from data loaded from a module. Each item from the array will have it's own describe statement with certain protractor actions. My previous post has an answer that says to use an it statement, but I can't do that because there's too much going on.
My main problem is that the data doesn't get loaded in time for the describe. I had another suggestion to use VCR.js or something similar but I don't think those will work because I'm using a module. Is there a way I can save the data to a separate file and load it in? Would that be a good way to go?
var data = require('get-data'); //custom module here
describe('Test', function() {
var itemsArr;
beforeAll(function(done) {
data.get(function(err, result) {
itemsArr = result; //load data from module
done();
});
})
//error: Cannot read property 'forEach' of undefined
describe('check each item', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});
UPDATE:
I used Eugene's answer and came up with this. I can't test each individual study how I want because the it statement doesn't fire. Is this problem even solvable??
describe('check each item', function () {
it('should load data', function (done) {
browser.wait(itemsPromise, 5000);
itemsPromise.then(function(itemsArr) {
expect(itemsArr).toBeTruthy();
studyArr = itemsArr.filter(function (item) {
return item.enabled && _.contains(item.tags, 'study');
});
studyCount = studyArr.length;
expect(studies.count()).toEqual(studyCount);
checkItems(studyArr);
done();
});
});
function checkItems (itemsArr) {
itemsArr.forEach(function (item) {
describe(item.id, function () {
console.log('checkItems', item.id);
// doesn't work
it('should work', function (done) {
expect(false).toBeTruthy();
done();
});
});
});
}
});
You're trying to do something that Jasmine does not allow: generating tests after the test suite has started. See this comment on an issue of Jasmine:
Jasmine doesn't support adding specs once the suite has started running. Usually, when I've needed to do this, I've been able to know the list of options ahead of time and just loop through them to make the it calls. [...]
("adding specs" === "adding tests")
The point is that you can generate tests dynamically but only before the test suite has started executing tests. One corollary of this is that the test generation cannot be asynchronous.
Your second attempt does not work because it is trying to add tests to a suite that is already running.
Your first attempt is closer to what you need but it does not work either because describe calls its callback immediately, so beforeAll has not run by the time your describe tries to generate the tests.
Solutions
It all boils down to computing the value of itemsArr before the test suite start executing tests.
You could create a .getSync method that would return results synchronously. Your code would then be something like:
var data = require('get-data'); //custom module here
var itemsArr = data.getSync();
describe('Test', function() {
describe('check each item', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
[...]
If writing .getSync function is not possible, you could have an external process be responsible for producing a JSON output that you could then deserialize into itemsArr. You'd execute this external process with one of the ...Sync functions of child_process.
Here's an example of how the 2nd option could work. I've created a get-data.js file with the following code which uses setTimeout to simulate an asynchronous operation:
var Promise = require("bluebird"); // Bluebird is a promise library.
var get = exports.get = function () {
return new Promise(function (resolve, reject) {
var itemsArr = [
{
name: "one",
param: "2"
},
{
name: "two",
param: "2"
}
];
setTimeout(function () {
resolve(itemsArr);
}, 1000);
});
};
// This is what we run when were are running this module as a "script" instead
// of a "module".
function main() {
get().then(function (itemsArr) {
console.log(JSON.stringify(itemsArr));
});
};
// Check whether we are a script or a module...
if (require.main === module) {
main();
}
Then, inside the spec file:
var child_process = require('child_process');
var itemsArr = JSON.parse(child_process.execFileSync(
"/usr/bin/node", ["get-data.js"]));
describe('Test', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});
I've tested the code above using jasmine-node. And the following file structure:
.
├── data.js
├── get-data.js
└── test
└── foo.spec.js
./node_modules has bluebird and jasmine-node in it. This is what I get:
$ ./node_modules/.bin/jasmine-node --verbose test
describe
describe
it
it
Test - 5 ms
one - 4 ms
should work - 4 ms
two - 1 ms
should work - 1 ms
Finished in 0.007 seconds
2 tests, 2 assertions, 0 failures, 0 skipped
Try to use a promise, something like:
var deferred = protractor.promise.defer();
var itemsPromise = deferred.promise;
beforeAll(function() {
data.get(function(err, result) {
deferred.fulfill(result);
});
})
And then:
describe('check each item', function() {
itemsPromise.then(function(itemsArr) {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
});
Another solution I can think of is to use browser.wait to wait until itemsArr becomes not empty.
Is your get-data module doing some browser things with protractor? If so, you will need to set/get itemsArr within the context of the controlFlow. Otherwise it will read all the code in the get-data module, but defer its execution and not wait for it to finish before moving right along to those expect statements.
var data = require('get-data'); //custom module here
var itemsArr;
describe('Test', function() {
beforeAll(function() {
// hook into the controlFlow and set the value of the variable
browser.controlFlow().execute(function() {
data.get(function(err, result) {
itemsArr = result; //load data from module
});
});
});
//error: Cannot read property 'forEach' of undefined
describe('check each item', function() {
// hook into the controlFlow and get the value of the variable (at that point in time)
browser.controlFlow().execute(function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});
So I have a folder full of scripts that all resemble a structure like this
// Adapter-100.js
angular.module('myModule', ['myParentFactory', function(myParentFactory) {
return angular.extend(myParentFactory, {
"someFunctionA" : function() {},
"someFunctionB" : function() {},
"someFunctionC" : function() {}
});
}]);
And my test just checks that they have these three methods, trouble is there is about 100 of these files (they're adapters for communicating with a server)
Here is a representation of my tests file
// api-adapter-tests.js
describe('Unit: EndPointMethods', function() {
var values, factory, adapter;
// Boot the module
beforeEach(function() {
module('myModule');
inject(function ($injector) {
values = $injector.get('AppConsts');
factory = $injector.get('EndPointConnection');
adapter = $injector.get('TestAdapter'); // This needs to change to match what adapter is being tested
});
});
// Run some tests
describe('AppConsts', function() {
it('Should have an api_host key', function() {
expect(values).toBeDefined();
expect(values.api_host).toBeDefined();
expect(typeof values.api_host).toBe('string');
});
});
// Is this able to be generated to test each adapter independently?
describe('TestEndPointMethod has minimum functional definitions', function() {
it('should have 3 defined functions', function() {
expect(factory.consumeResponse).toBeDefined();
expect(factory.getEndPoint).toBeDefined();
expect(factory.mutator).toBeDefined();
});
});
});
I don't want to have to write a separate describes/it block for each adapter but rather have Karma loop over all of these and create the tests on the fly (the tests are very unlikely to ever change)
I've Googled around for a solution to this but can't seem to find anything that does this kind of thing for me.
You can wrap your suites in a clojure and pass the Adapter you want to test: mocha will take care of running it in the right way - and so Karma.
function runSuiteFor(newAdapter){
return function(){
// api-adapter-tests.js
describe('Unit: EndPointMethods', function() {
var values, factory, adapter;
// Boot the module
beforeEach(function() {
module('myModule');
inject(function ($injector) {
values = $injector.get('AppConsts');
factory = $injector.get('EndPointConnection');
adapter = $injector.get(newAdapter); // set the Adapter here
});
});
// Run some tests
describe('AppConsts', function() {
it('Should have an api_host key', function() {
expect(values).toBeDefined();
expect(values.api_host).toBeDefined();
expect(typeof values.api_host).toBe('string');
});
});
// Is this able to be generated to test each adapter independently?
describe('TestEndPointMethod has minimum functional definitions', function() {
it('should have 3 defined functions', function() {
expect(factory.consumeResponse).toBeDefined();
expect(factory.getEndPoint).toBeDefined();
expect(factory.mutator).toBeDefined();
});
});
});
}
}
var adapters = ['MyTestAdapter1', 'MyTestAdapter2', etc...];
for( var i=0; i<adapters.length; i++){
runSuiteFor(adapters[i])();
}
Note: IE8 has some issues with this approach sometimes, so in case you're with Angular 1.2 bare in mind this.
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
});
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!