Hi i am a newbie in javascript. I am implementing emitter on function in javascript and i have trouble in testing it using jasmine framework. Below is the function emitter on and code to test the function.
//main.js
Emitter.prototype.on = function (event, listener)
{
if (typeof this.events[event] !== 'object')
{
this.events[event] = [];
}
this.events[event].push(listener);
};
//test.js
describe('#on', () =>
{
it('should subscribe to an event name and be called when
triggered', () =>
{
Emitter.on(EVENT_NAME_ONE, spyFunction);
//Emitter.trigger(EVENT_NAME_ONE);
expect(spyFunction.calls.count()).toBe(1);
});
The above mentioned test fails. I am not sure why. Can someone please help me with this. Thanks.
The error says Emitter.on is undefined. See this thread.
on is defined on Emitter instance.
class Emitter {
constructor() {
console.log('created Emitter!')
}
on(event, callback) {
console.log('on')
}
}
a = new Emitter()
=> created Emitter!
a.on()
=> on
I could solve that error. i had to export the function to the tests and changed the browser to chrome in karma.conf.js file . Now i get this TypeError: Cannot read property 'event_name_one' of undefined. I have it defined
var Emitter = function () {
this.events = {};
var Event_Name_One;
} ;
export function on (event, listener) {
if (typeof this.events[event] !== 'object') {
this.events[event] = [];
}
this.events[event].push(listener);
};
and in test.js
// Testing event names
const EVENT_NAME_ONE = 'event_name_one';
describe('#on', () => {
it('should subscribe to an event name and be called when triggered', () => {
Emitter.on(EVENT_NAME_ONE, spyFunction);
Emitter.trigger(EVENT_NAME_ONE);
expect(spyFunction.calls.count()).toBe(1);
});
I don't understand why i get this error. Can anyone provide help regarding this.thanks.
Related
Here is my function to recursively loop through a object:
const helper = (obj,list) =>{
for (var property in obj){
if(obj.hasOwnProperty(property)) {
if(typeof obj[property] == "object") {
helper(obj[property],list);
}else {
if (property === "$ref"){
if(!list.includes(obj[property])){
list.push(obj[property]);
}
}
}
}
}
return list;
};
The object is simple, please see below:
{
"person": {
"properties": {
"sex":{
"type":"string"
},
"name": {
"$ref": "#/person/properties/sex"
}
}
}
}
The the helper will finally return a list ['#/person/properties/sex']
To run the code, just do helper(some_obj,[])
Here is my jest test code:
describe('helper function test', () =>{
it('should return a list', () =>{
const mock = jest.fn((obj,[]) => helper(obj,[]));
const list = mock(obj,[]);
expect(list).toMatch(['something']);
});
});
I have also tried:
describe('helper function test', () =>{
it('should return a list', () =>{
const list = helper(obj, []);
expect(list).toMatch(['something']);
});
});
The jest tells me the expect object is a array but it's value is [], which means empty. Actually I did a manually test for helper function in the function file, the return has no problem, which is what I expect.
One thing to mention, I use this helper inside of a promise later, I do not know the issue is related to promise, since the promise function has not called. I even tried to comment out the promise function, still no luck.
Would someone tells me how to get the real result from the jest? I would really appreciate any helps here! Thank you for your time.
I don't think you need to use jest.fn in here. If you want to test your function, you can do this:
describe('helper function test', () =>{
it('should return a list', () =>{
const list = helper(obj, []);
expect(list).toEqual(['something']);
});
});
My unit test is not setup properly, meaning that I am not correctly mocking my imported plugin function.
How do I correctly mock my logData function? In the plugin the function returns undefined and that is by choice. I just need to make sure I console.log whatever is passed to it.
The current error I get is "Cannot spy the logData property because it is not a function; undefined given instead"
logData.js - the plugin (just a wrapper around console.log statements)
export function logData (dataToLog) {
const isLoggingData = localStorage.getItem('isLoggingData')
if (isLoggingData) {
console.log(dataToLog)
}
}
export default {
install: function (Vue) {
Vue.prototype.$logData = logData
}
}
logData.spec.js - I mocked localStorage but I need to mock the plugin logData
import Vue from 'vue'
import { createLocalVue } from '#vue/test-utils'
import logData from './logData'
class LocalStorageMock {
constructor () {
this.store = {}
}
getItem (key) {
return this.store[key] || null
}
setItem (key, value) {
this.store[key] = value.toString()
}
removeItem (key) {
delete this.store[key]
}
}
global.localStorage = new LocalStorageMock()
const localVue = createLocalVue()
const dataToLog = 'data to be logged'
localVue.use(logData)
const mockLogDataFunction = jest.spyOn(localVue.prototype, 'logData')
describe('logData plugin', () => {
// PASSES
it('adds a $logData method to the Vue prototype', () => {
console.log(wrapper.vm.$logData)
expect(Vue.prototype.$logData).toBeUndefined()
expect(typeof localVue.prototype.$logData).toBe('function')
})
// Now passes
it('[positive] console.logs data passed to it', () => {
global.localStorage.setItem('isLoggingData', true)
const spy = jest.spyOn(global.console, 'log')
localVue.prototype.$logData('data to be logged')
expect(spy).toHaveBeenCalledWith(dataToLog)
// expect(mockLogDataFunction).toHaveBeenCalled()
// console.log(localVue)
// expect(localVue.prototype.$logData(dataToLog)).toMatch('data to be logged')
})
// PASSES
it('[negative] does not console.log data passed to it', () => {
const spy = jest.spyOn(global.console, 'log')
global.localStorage.removeItem('isLoggingData')
localVue.prototype.$logData(dataToLog)
expect(spy).not.toHaveBeenCalled()
// const spy = jest.spyOn(this.$logData, 'console')
// expect(localVue.prototype.$logData(dataToLog)).toBe(und efined)
// expect(spy).toBe(undefined)
})
})
You are doing things wrong.
jest.spyOn(object, methodName). You need to pass localVue.prototype and 'logData', as arguments, to track if this method was called.
Creates a mock function similar to jest.fn but also tracks calls to
object[methodName]. Returns a Jest mock function.
To check if method was called – expect(spy).toHaveBeenCalled().
So change some lines of your code:
const mockLogDataFunction = jest.spyOn(localVue.prototype, '$logData')
// invoke it and then ensure that that method is really called
localVue.prototype.$logData('foo')
expect(mockLogDataFunction).toHaveBeenCalled()
How can I test a property inside a timeout from another called method?
I want to test a property if it's changed inside the setTimeout but using sinons useFakeTimer doesn't seems to work. Or am I missing something?
To illustrate here's my code
const fs = require('fs');
function Afunc (context) {
this.test = context;
}
module.exports = Afunc;
Afunc.prototype.start = function () {
const self = this;
this.readFile(function (error, content) {
setTimeout(function () {
self.test = 'changed';
self.start();
}, 1000);
});
}
Afunc.prototype.readFile = function (callback) {
fs.readFile('./file', function (error, content) {
if (error) {
return callback(error);
}
callback(null, content);
})
}
And here's what I have so far.
describe('Afunc', function () {
let sandbox, clock, afunc;
before(function () {
sandbox = sinon.createSandbox();
});
beforeEach(function () {
clock = sinon.useFakeTimers();
afunc = new Afunc('test');
sandbox.stub(afunc, 'readFile').yieldsAsync(null);
});
afterEach(function () {
clock.restore();
sandbox.restore();
});
it('should change test to `changed`', function () {
afunc.start();
clock.tick(1000);
afunc.test.should.be.equal('changed');
});
});
after the clock.tick check the property test is not changed.
Any help is deeply appreciated! Thanks in advance.
Just change this:
sandbox.stub(afunc, 'readFile').yieldsAsync(null);
...to this:
sandbox.stub(afunc, 'readFile').yields();
...and it should work.
Details
yieldsAsync defers using process.nextTick so the callback passed to readFile wasn't getting called until "all instructions in the current call stack are processed"...which in this case was your test function.
So the callback that changed afunc.test to 'changed' was getting called...but not until after your test completed.
I have a object that is subscribed to the uncaught error event and I am trying to test its behaviour. First I tried with jasmine-node, but now when I am trying to go with jasmine I found trouble. Could anyone help me.
describe('Constructor tests', function () {
it('error is passed to the callback', function (done) {
const error = new Error("testError-0");
let errorHandler = new AllErrorHandler((arg1) => {
expect(arg1).toBe(error);
errorHandler.dispose();
done();
});
setTimeout(() => {
throw error;
}, 0)
});
Thanks in advance.
I got this working when executed directly via jasmine when the jasmine ./tests/alLErrorException.spec.js command is run. The following changes were required:
Always setup the listeners, even when the _callback should not be executed.
constructor(callback, startListening = true) {
if (!callback) {
throw new Error("Missing callback function");
}
this._callback = callback;
this._listening = startListening;
this._setupEvents();
}
Add a function to intercept uncaughtException events and to call the _callback if we are _listening:
_handler() {
if(this._listening) {
this._callback.apply(null, arguments);
}
}
Remove all other uncaughtException event handlers in _setupEvents:
_setupEvents(attatch = true) {
this._listening = attatch ? true : false;
if (attatch) {
if (typeof window !== "undefined") {
window.addEventListener("error", this._callback);
} else {
// Added this line
process.removeAllListeners('uncaughtException');
process.addListener('uncaughtException', this._handler.bind(this));
}
} else {
if (typeof window !== "undefined") {
window.removeEventListener("error", this._callback);
} else {
process.removeListener('uncaughtException', this._callback);
}
}
}
This is required because jasmine sets up it's own uncaughtException handler and reports an error even though the error was caught by the AllErrorHandler class.
Here is a paste of the full source for the AllErrorHandler class with the required changes.
Im testing a publish method on a pub sub class. I am creating a callback function within the beforeEach function and subscribing to the class. In the it method I am publishing the event and attempting to test that the callback was called which is basically how the class works. I have got the test working and it passes but the problem is I had to use a setTimeout to get this to work. I believe this is probably not the right way to do this.
describe('publish', () => {
let testpublish;
let callback;
beforeEach(() => {
callback = function(data) { return data + 10; }
testpublish = {
'id': 'testpublish1',
'event': 'testpublish',
'callback': callback
};
subject.subscribe(testpublish);
});
it('should call the subscription function', () => {
subject.publish('testpublish', 9);
setTimeout(() => {
expect(callback).toEqual(19);
});
});
});
I initially wanted to spy on the callback just to see if it was called but the documentation for Jasmine says I must place my method in an object:
spyOn(obj, methodName) → {Spy}
Any advice on a better way to do this would be appreciated. Thanks.
PubSub Class if useful ??
#Injectable()
export class Pubsub {
private events: any = {};
public subscribe(config: any) {
let event = config['event'];
this.events[event] = this.events[event] || [];
if (this.events[event].length < 1) {
this.events[event].push(config);
} else {
for (let i = 0; i < this.events[event].length; i++) {
if (this.events[event][i].id !== config.id) {
this.events[event].push(config);
}
}
}
}
public unsubscribe(obj: Object) {
let event = obj['event'];
let id = obj['id'];
if (this.events[event]) {
this.events[event] = this.events[event].filter((eventObj) => {
return eventObj.id !== id;
});
}
if (this.events[event].length === 0) {
delete this.events[event];
}
}
public publish(event: string, data: any) {
if (this.events[event]) {
this.events[event].forEach(function(obj) {
obj.callback(data);
});
}
}
public getEvents() {
return this.events;
}
}
Existing function cannot be spied, because a spy is a new function, and the reference to original function is already used in the place where it is being called.
Considering that callback function is defined in the test itself, not inside the application, it should be defined as a spy in the first place:
callback = jasmine.createSpy();
It doesn't even have to do something because its return value doesn't add value to the test.
And it is tested like
const arg = {};
subject.publish('testpublish', arg);
expect(callback.calls.count()).toBe(1);
expect(callback.calls.first().args[0]).toBe(arg);
publish is synchronous, as well the rest of the class. There's no need for setTimeout, and it is harmful here. When done parameter isn't specified for the test, it is considered synchronous, and setTimeout makes assertions ignored in this test.
This
it('should pass', () => {
setTimeout(() => {
expect(1).toBe(2);
});
});
will always pass. And only if if the suite has no other tests, this will trigger SPEC HAS NO EXPECTATIONS warning.
jasmine.createSpy('spy') will do work.
describe('publish', () => {
let testpublish;
let callback;
let subject = new Pubsub();
beforeEach(() => {
callback = function (data) {
return data + 10;
}
testpublish = {
'id': 'testpublish1',
'event': 'testpublish',
'callback': jasmine.createSpy('spy')
};
subject.subscribe(testpublish);
});
it('should call the subscription function', () => {
subject.publish('testpublish', 9);
expect(testpublish.callback).toHaveBeenCalledWith(9);
});
});