As the title mentioned. I would like to ask that how could i write the test code for this scenario?
Example... i have a class look like this.
let ABC = function()
{
this.title="sample"
};
Question is how do i use chai expect to check for the existences of ABC class?
Does expect chai do something like this?
Expect(ABC).to.be.a.class
I'm not sure if you had this answered yet but I had the same question and I just found out
so after requiring chai and the expect API
and after requiring the file where your class resides and setting up your test
you can ensure that:
it is a function with .to.be.a('function');
check that it is an instance of your class with
expect(className).to.be.an.instance.of(ClassName);
const chai = require('chai');
const expect = chai.expect;
const Card = require('../src/Card');
describe('Card', function () {
it.skip('should be a function', function () {
const card = new Card();
expect(Card).to.be.a('function');
});
it.skip('should be an instance of Card', function () {
const card = new Card();
expect(card).to.be.an.instanceof(Card);
})
})
How can I test if a callback function from a event listener is called? For example, I have the following code where app.js initializes the application through the init.js controller.
The main.js file has a class which extends and Event Emitter, making the object an event emitter.
app.js
const initController = require('./init');
async function init() {
initController.startMain();
}
init();
main.js
const events = require('events'),
ui = require('./ui');
module.exports.getMain = function () {
class Main extends events.EventEmitter {
constructor() {
super();
this.status = null;
}
}
return new Main();
};
module.exports.init = () => {
const main = this.getMain();
ui.init(main);
this.start(main);
}
module.exports.start = (main) => {
ui.start(main);
main.emit('http-init');
main.emit('http-success');
main.emit('http-error');
};
ui.js
function init(main) {
main.on('http-init', onHttpInit.bind(this));
main.on('http-success', onHttpSuccess.bind(this));
main.on('http-error', onHttpError.bind(this));
main.once('app-ready', onAppReady.bind(this));
};
function start (main) {};
function onAppReady() {
console.log('APP READY');
};
function onHttpInit() {
console.log('HTTP INIT SEQUENCE');
};
function onHttpError(error) {
console.log('HTTP ERROR SEQUENCE');
};
function onHttpSuccess() {
console.log('HTTP SUCCESS SEQUENCE');
};
module.exports = exports = {
init,
start,
onHttpInit,
onHttpError,
onHttpSuccess,
};
init.js
exports.startMain = () => {
console.log('Start application');
// Load application modules
const main = require('./main');
// Start the application
main.init();
};
So, when I run the command node app.js, I see the following output
Start application
HTTP INIT SEQUENCE
HTTP SUCCESS SEQUENCE
HTTP ERROR SEQUENCE
which means that the listeners are active and that the functions are called.
ui.tests.js
const sinon = require('sinon'),
main = require('../main').getMain(),
proxyquire = require('proxyquire').noPreserveCache().noCallThru();
describe('UI Tests', () => {
const sandbox = sinon.createSandbox();
let controller = null;
before(() => {
controller = proxyquire('../ui', {});
})
describe('Testing Eventlisteners', ()=> {
afterEach(() => {
main.removeAllListeners();
});
const eventMap = new Map([
[ 'http-init', 'onHttpInit' ],
[ 'http-success', 'onHttpSuccess' ],
[ 'http-error', 'onHttpError']
]);
eventMap.forEach((value, key) => {
it(`should register an eventlistener on '${key}' to ${value}`, () => {
const stub = sinon.stub(controller, value);
controller.init(main);
main.emit(key);
sinon.assert.called(stub);
})
})
})
})
However, when I run the above test, even though I get the output, i.e. the functions were called, however, sinon assert always fails saying the below:
UI Tests
Testing Eventlisteners
HTTP INIT SEQUENCE
1) should register an eventlistener on 'http-init' to onHttpInit
HTTP SUCCESS SEQUENCE
2) should register an eventlistener on 'http-success' to onHttpSuccess
HTTP ERROR SEQUENCE
3) should register an eventlistener on 'http-error' to onHttpError
0 passing (16ms)
3 failing
1) UI Tests
Testing Eventlisteners
should register an eventlistener on 'http-init' to onHttpInit:
AssertError: expected onHttpInit to have been called at least once but was never called
at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
at Context.it (test/ui.tests.js:25:30)
2) UI Tests
Testing Eventlisteners
should register an eventlistener on 'http-success' to onHttpSuccess:
AssertError: expected onHttpSuccess to have been called at least once but was never called
at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
at Context.it (test/ui.tests.js:25:30)
3) UI Tests
Testing Eventlisteners
should register an eventlistener on 'http-error' to onHttpError:
AssertError: expected onHttpError to have been called at least once but was never called
at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
at Context.it (test/ui.tests.js:25:30)
I do not know why the tests fail even though the function was called at least once, which is seen by the outputs HTTP INIT SEQUENCE, HTTP SUCCESS SEQUENCE and HTTP ERROR SEQUENCE when I run the tests.
I tried doing stub.should.have.been.called;. With this the tests pass, however, it's not really passing the tests as both stub.should.have.been.called; or stub.should.not.have.been.called; pass the test regardless, instead of the latter failing the test.
Anybody know the reason for this failing test? Thank you for any help.
You run const stub = sinon.stub(controller, value); to stub the values exported by your ui module. This does change the values exported by the module, but the problem is with this code inside your ui module:
function init(main) {
main.on('http-init', onHttpInit.bind(this));
main.on('http-success', onHttpSuccess.bind(this));
main.on('http-error', onHttpError.bind(this));
main.once('app-ready', onAppReady.bind(this));
}
From the perspective of this code module.exports is mutated by your calls sinon.stub(controller, value) but this does not change the values of the symbols onHttpInit, onHttpSuccess, etc. symbols in the code above because these are symbols that are local to the scope of the ui module. You can mutate module.exports as much as you want: it still has no effect on the code above.
You could change your code to this:
function init(main) {
main.on('http-init', exports.onHttpInit.bind(this));
main.on('http-success', exports.onHttpSuccess.bind(this));
main.on('http-error', exports.onHttpError.bind(this));
main.once('app-ready', exports.onAppReady.bind(this));
}
You can use exports directly because you assign the same value to both module.exports and exports with module.exports = exports = ...
This change should fix the immediate issue you ran into. However, I'd modify the testing approach here. Your tests are titled should register an eventlistener on '${key}' to ${value} but really what your are testing is not merely that an event listener has been registered but that event propagation works. In effect, you are testing the functionality of that EventEmitter is responsible for providing. I'd change the tests to stub the on method of your main object and verify that it has been called with the appropriate values. Then the test would test what it actually advertises.
you are registering the main callbacks prior to stubbing, so the stubbed functions are not what is called, only the original functions. Try reversing the order in your it function:
eventMap.forEach((value, key) => {
it(`should register an eventlistener on '${key}' to ${value}`, () => {
const stub = sinon.stub(controller, value);
controller.init(main);
main.emit(key);
sinon.assert.called(stub);
})
})
It also appears you are requiring main without using proxyquire. So then it would never be picking up the stub. A couple solutions: 1) rework main to take UI as a argument (i.e. dependency injection) in which case your tests could pass the stub to main; or 2) require main with proxyquire so you can force it to require the stubbed version. Let me know if you need more details.
Ok I do not about sinon, but the jest has same functionality called mock functions.
And jest has faced the same issue due to export https://medium.com/#DavideRama/mock-spy-exported-functions-within-a-single-module-in-jest-cdf2b61af642. Because of your export getMain,init and start in main.js and using getMain and start inside init.
Instead try to move getMain and start to separate module and export and test it. Let me know if issues still appears
After a week of questions and tests, I have found a solution. It was a bit of a combination of solutions from DDupont and Louis. The first change the following, in the ui.js file, add this. to the bind
function init(main) {
main.on('http-init', this.onHttpInit.bind(this));
main.on('http-success', this.onHttpSuccess.bind(this));
main.on('http-error', this.onHttpError.bind(this));
main.once('app-ready', this.onAppReady.bind(this));
};
And like DDupont said, in the unit test, move controller.init(main) after the stub
eventMap.forEach((value, key) => {
it(`should register an eventlistener on '${key}' to ${value}`, () => {
const stub = sinon.stub(controller, value);
controller.init(main);
main.emit(key);
sinon.assert.called(stub);
})
})
Thank you for all the help.
I would like to add custom methods to the puppeteer.Page object, so I could invoke them like so:
let page = await browser.newPage();
page.myNewCustomMethod();
Here is one out of many custom methods I have created. It finds first available element by the XPath expression, using the array of expressions:
const findAnyByXPath = async function (page: puppeteer.Page, expressions: string[]) {
for (const exp of expressions) {
const elements = await page.$x(exp);
if (elements.length) {
return elements[0];
}
}
return null;
}
I have to invoke it like so...
let element = await findAnyByXPath(page, arrayOfExpressions);
To me, that looks weird in the editor, especially in a region where many custom methods are being invoked. It looks to me, a bit of "out of context". So I would rather invoke it like that:
page.findAnyByXPath(arrayOfExpressions);
I'm aware that there is a page.exposeFunction method, but it is not what I'm looking for.
What is a way to achieve this?
Can you do this? Yes.
You can extend any object in JavaScript by modifying its prototype. In order to add a function to a Page object, you can access the prototype of a Page object by using the __proto__ property.
Here is a simple example adding the function customMethod to all Page objects:
const page = await browser.newPage();
page.__proto__.customMethod = async function () {
// ...
return 123;
}
console.log(await page.customMethod()); // 123
const anotherPage = await browser.newPage();
console.log(await anotherPage.customMethod()); // 123
Note, that you need a Page object first, to access the prototype as the constructor function (or class) is not itself exposed.
Should you do this? No.
You probably already noticed the red warnings on the linked MDN docs above. Read them carefully. In general, it is not recommended to change the prototype of objects you are using and haven't created yourself. Someone has created the prototype and he did not expect anyone to tinker around with it. For further information check out this stackoverflow question:
"Why is extending native objects a bad practice?"
How to do it instead?
Instead, you should just use your own functions. There is nothing wrong with having your own functions and call them with page as argument like this:
// simple function
findAnyByXPath(page);
// your own "namespace" with more functionality
myLibrary.findAnyByXPath(page);
myLibrary.anotherCustomFunction(page);
Normally, you could also extend the class Page, but in this case the library is not exporting the class itself. Therefore, you can only create a wrapper class which executes the same functions inside but offers more functionality on top. But this would be a very sophisticated approach and is really worth the effort in this case.
To expand on #Thomas's answer, if you want to override an original method of Page:
const extendPage = (page: Page) => {
const { goto: originalGoto } = page;
page.goto = function goto(url, options) {
console.log("Goto:", url);
// do your things
return originalGoto.apply(page, arguments);
};
return page;
};
const page = extendPage(await browser.newPage());
await page.goto("https://google.com"); // Goto: https://www.google.com
To attach additional methods every time a new Page is created, you can listen to the targetcreated event from the Browser and extend the page in the callback:
const browser = await puppeteer.launch();
browser.on("targetcreated", async (target: Target) => {
if (target.type() === "page") {
const page = await target.page();
extendPage(page);
}
});
const page = await browser.newPage(); // extended page
If you want to add a new method and update Typescript definition:
import { Page, PageEmittedEvents } from "puppeteer";
async function htmlOnly(this: Page) {
await this.setRequestInterception(true); // enable request interception
this.on(PageEmittedEvents.Request, (req) => {
if (req.resourceType() === 'document') return req.continue();
return req.abort();
});
}
declare module "puppeteer" {
interface Page {
htmlOnly: () => Promise<void>;
}
}
export const extendPage = (page: Page) => {
page.htmlOnly = htmlOnly;
return page;
};
browser.on("targetcreated", async (target: Target) => {
if (target.type() === "page") {
const page = await target.page();
extendPage(page);
}
});
const page = await browser.newPage();
await page.htmlOnly();
I'm struggling with using spyOn as part of testing my utils.js module. I've tried various methods and approaches but all seem to yield the "expected mock function to have been called". For the record, other unit tests work OK, so there shouldn't be any issue with my actual test setup.
Below is a simplified test case with two functions and one test, and I can't even get these to work. Did I misunderstand the spyOn altogether?
// utils.js
function capitalHelper(string){
return string.toUpperCase();
}
function getCapitalName(inputString){
return capitalHelper(inputString.charAt(0)) + inputString.slice(1);
}
exports.capitalHelper = capitalHelper
exports.getCapitalName = getCapitalName
// utils.test.js
const Utils = require('./utils');
test('helper function was called', () => {
const capitalHelperSpy = jest.spyOn(Utils, 'capitalHelper');
const newString = Utils.getCapitalName('john');
expect(Utils.capitalHelper).toHaveBeenCalled();
})
I do ont use spyOn(), but jest.fn() instead for all mock scenario
In your case I would do the following
test('helper function was called', () => {
Utils.capitalHelper = jest.fn((s) => Utils.capitalHelper(s))
const newString = Utils.getCapitalName('john')
expect(Utils.capitalHelper.mock.calls.length).toBe(1)
})
First line could have simply be :
Utils.capitalHelper = jest.fn()
since you don't seem to be testing the returned value in your test :)
You can find more details on jest.fn() on the jest official documentation : https://facebook.github.io/jest/docs/en/mock-functions.html
----------------------- EDIT
I got it : the problem occurs because within your utils.js file, getCapitalName uses the defined function, not the one pointed by the export.
To be able to mock the function in use you could change your utils.js file to
// utils.js
const Utils = {
capitalHelper: string => string.toUpperCase(),
getCapitalName: inputString => Utils.capitalHelper(inputString.charAt(0)) + inputString.slice(1)
}
export default Utils
then the tests I gave before will work
I have a function, is a function of polymer web component custom.
getListDedications: function(e){
var idDate = e.model.__data__.date.data.id;
var checkId = function(element){
return element.id == idDate;
};
var responseID = this.dedications.filter(checkId);
this.data = JSON.stringify(responseID[0].entries) || [];
console.log(JSON.stringify(responseID[0].entries) || []);
}
This function return a array or an array empty.
I want to test it, I'm using web-component-tester and I run the tests with gulp test:local.
I know that I need to mock e.model.__data__.date.data.id but I do not know how
Web component tester comes with sinon and sinon-chai bundled in it now.
You don't say what e.model._data__.date.date.id is. Obviously if its just data, you set it up and then call getListModifications with the a parameter. However, if its a function then use sinon stub (or spy) with
var sandbox;
beforeEach(function(){
sandbox = sinon.sandbox.create();
});
afterEach(function(){
sandbox.restore();
});
it('should ...',function(){
var e = sandbox.stub().returns('whatever data you want');
var answer = getListDedications(e);
expect(answer).to.be.an('Array');
});