NodeJS Unit test how to spy/stub - node.js

I am stuck on this for few days now.
while testing my handler function I would like to "fake" the call to rotateApiKeys fonction I was thinking to use stubs to achieve that.
But first I would like to see if at least I could spy on rotateApiKeys while calling the handler, so far I am getting:
AssertError: expected rotateApiKeys to be called once but was called 0 times and I can see that function actually been called.
Questions:
What would you recommend me to use? Mock/Stub/Spy
If Spy should work, why I am getting that assertError?
Handler:
async function handler(event) {
// declare a new JSON object
let handlerObject = {
"event": event,
"isValidEvent": rotateFunctions.validateEvent(event),
"actionCountObject": {}
};
if (handlerObject.isValidEvent) {
// continue here
handlerObject.actionCountObject = await rotateApiKeys(event);
}
// console log JSON handlerObject
console.log("handlerObject: " + JSON.stringify(handlerObject));
// return the object
return handlerObject;
}
unit test code:
it("Should call rotate", async function() {
var rotate = sinon.spy(rotateApiKeys, 'rotateApiKeys');
const result = await rotateApiKeys.handler(event);
rotate.restore();
sinon.assert.calledOnce(rotate);
});

Related

Jest spy.On() does not call the method in React Js

I write a test for a method:
const methods = {
run: (name) => {
console.log('run');
return name;
}
}
Using const testMethod = jest.spyOn(methods, 'run').mockResolvedValue({});, the console.log('run') is not triggered, but if i write: const testMethod = jest.spyOn(methods, 'run');, the console.log() is triggered. Why in the first case the console.log() is not triggered and how to solve this?
When you use mockResolvedValue, you are replacing your function with a stub. As the purpose of a stub is not to execute the real implementation of the function but just to return a fictitious value, this behavior is normal.
jest.fn().mockResolvedValue({})
is equivalent to:
jest.fn().mockImplementation(() => Promise.resolve({}));
https://jestjs.io/docs/mock-function-api#mockfnmockresolvedvaluevalue
Update:
If you want to verify if your function is called and if it returned a specific value, then:
const spy = jest.spyOn(methods, 'run');
const myName = 'John Doe';
// Call the method...
expect(spy).toBeCalled();
expect(spy).toHaveReturnedWith(myName);

How to do callback in our component using react jest test cases

How can we do callback on success and failue cases for below lines of code for test coverage using jest
const handleService = () => {
window.domain.service("1321",'',onSuccess, onFailure)
}
const onSuccess = () => {
....update state values
}
const onFailure = () => {
....update state values
}
Something like this:
Spy on window.domain.service to gain access to the calls it receives. This will allow you to access the parameters of those calls which will be "1321",'',onSuccess, onFailure
Assign the function you wish to test to a variable
Invoke the function to execute the code in it (this will get you the coverage)
(Optional) assert that the callback functions behave correctly
Here is a snippet to help demonstrate
it('should run', () => {
// Some setup to create the function on the window, may not be needed if done elsewhere.
// Could be good to do this in a beforeEach and clean up in afterEach to avoid contaminating the window object
window.domain = {
service: () => {},
}
// Spy on the window.domain.service method.
// Provide a mock implementation if you don't want the real one to be called
const serviceSpy = jest.spyOn(window.domain, 'service');
executeYourCode();
// capture the arguments to the call
const [_arg1, _arg2, onSuccess, onFailure] = serviceSpy.mock.calls[0];
// execute the callbacks
onSuccess();
onFailure();
});

Why is the second Jest mock function never being called?

I am mocking navigator functions for simple clipboard functionality. Here is the relevant code:
// FUNCTION
/**
* Adds a click event to the button which will save a string to the navigator clipboard. Checks for
* clipboard permissions before copying.
*/
function loader(): void {
async function copyUrl(): Promise<void> {
const permission = await navigator.permissions.query({ name: "clipboard-write" });
if (permission.state == "granted" || permission.state == "prompt" ) {
await navigator.clipboard.writeText("the url");
} else {
console.error('Permission not supported');
}
}
const button = document.querySelector('button') as HTMLElement;
button.addEventListener('click', async () => {
await copyUrl();
});
}
// TEST
it('works', () => {
// mock navigator functions
Object.assign(navigator, {
permissions: {
query: jest.fn(async () => ({ state: "granted" }))
},
clipboard: {
writeText: jest.fn(async () => {})
}
});
// initialize DOM
document.body.innerHTML = '<button></button>';
loader(); // adds the event listener
// click the button!
const button = document.querySelector('button') as HTMLElement;
button.click();
expect(navigator.permissions.query).toHaveBeenCalledTimes(1);
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url');
});
The test fails on expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url') with:
Expected: "the url" Number of calls: 0
Defeats the purpose of permissions, yes, but for the sake of debugging:
Try adding a clipboard call before permissions call like so?
// FUNCTION
// ...
async function copyUrl(): Promise<void> {
// add this
await navigator.clipboard.writeText('the url');
// keep the rest still
const permission = await navigator.permissions.query({ name: "clipboard-write" });
// ...
}
This fails on the first assertion now, expect(navigator.permissions.query).toHaveBeenCalledTimes(1) with
Expected number of calls: 1 Received number of calls: 0
With the addition above, I also changed the assertions to be:
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url');
expect(navigator.clipboard.writeText).toHaveBeenCalledTimes(2);
expect(navigator.permissions.query).toHaveBeenCalledTimes(1);
... which failed on the second assertion because it expected 2 calls but only received 1.
I have been testing in a VSCode devcontainer and tried out the extension firsttris.vscode-jest-runner to debug the test. With breakpoints in the loader function, I'm able to see that every single line executes perfectly with my mockup but still fails at the end of debug.
I even changed the mock navigator.permissions.query function to return { state: 'denied' } instead. Both running and debugging, it did not satisfy the permission check and gave an error to the console as expected but the test still failed at expect(navigator.permissions.query).toHaveBeenCalledTimes(1) (with the added writeText call before it).
It seems to me that after the first call of a mock function, the others just don't work.
Am I missing something? Send help pls lol
EDITS
Using jest.spyOn as in this answer has the same issues.
Using an async test with an expect.assertions(n) assertion still produces the exact same issue.

Spying on a prototype method in Sinon.js

With sinon, I'm trying to spy on a prototype method. It looks so simple in online howtos etc. I tried many websites and SO posts like this: Stubbing a class method with Sinon.js or sinon - spy on toString method, but it just doesn't work for me.
Prerequisites
I'm using http.d.ts https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js to write data back from an async API call through an OutgoingMessage object:
class OutgoingMessage extends stream.Writable
There is a prototype method end in OutgoingMessage:
OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
My API function is called like this:
Get = async (req:IncomingMessage,res:OutgoingMessage):Promise<void> => {...
...
res.end(data)
...
}
My tests
In my test I'm calling the Get method. My IncomingMessage determines what I expect to be in the OutgoingMessage.
it("should call end with the correct value", async function(){
...
let outgoingMessageSpy = sinon.spy(OutgoingMessage.prototype,"end");
let anOutgoingMessage = <OutgoingMessage>{};
...
expect(outgoingMessageSpy.calledOnce).to.be.true();
}
Debugging the test case I see how end is being called but apparently I have not set up my spy the right way as my expectation fails, calledOnce is false. Inspecting the object I see that calledCount is 0.
I'm doing basically the same (it appears to me) when I do
const toStringSpy = sinon.spy(Number.prototype, "toString");
expect(toStringSpy.calledWith(2)).to.be.true;
and that works. I do notice, though, that VS Code highlights the keyword prototype differently for Number.prototype and OutgoingMessage.prototype. Is that of relevance? On mouse-over is says NumberConstructor.prototype but only OutgoingMessage.prototype..
Questions
How to set up the spy correctly to pick up the call to the prototype method end?
You have set up the spy correctly. BUT, there are conditions which can make the spy fail. For example: I saw you use async function, maybe you are not awaiting correctly on your async function test. Example below to show you that condition.
I have a very simple http server which has response with delay.
I create your Get method like this:
// File: get.ts
import { IncomingMessage, OutgoingMessage } from 'http';
import delay from 'delay';
const Get = async (req: IncomingMessage, res: OutgoingMessage): Promise<void> => {
console.log('Get start');
// I add this to show you that this async function need to be awaited.
await delay(200);
res.end(JSON.stringify({
data: 'Hello World!'
}));
console.log('Get finish');
};
export default Get;
And I create the main index.ts file.
// File: index.ts
import http, { IncomingMessage, OutgoingMessage } from 'http';
import Get from './get';
const server = http.createServer((req: IncomingMessage, res: OutgoingMessage) => {
console.log('Receiving IncomingMessage');
res.setHeader('Content-Type', 'application/json');
Get(req, res);
});
const port = 8000;
server.listen(port);
server.on('listening', () => {
console.log(`Listen http on ${port}`);
});
I create the test file: get.spec.ts
// File: get.spec.ts
import sinon from 'sinon';
import http, { OutgoingMessage } from 'http';
import { Socket } from 'net';
import { expect } from 'chai';
import Get from './get';
describe('GET', () => {
it('should call end with the correct value', async () => {
// I copy paste from your code with minor code style edit.
const outgoingMessageSpy = sinon.spy(OutgoingMessage.prototype, 'end');
const socket = new Socket();
const incoming = new http.IncomingMessage(socket);
const outgoing = new http.OutgoingMessage();
// This is just some private property which need to be set.
(outgoing as any)._header = true;
// NOTE: If you want invalid response, remove the "await".
await Get(incoming, outgoing);
// Set the debug message here before expect.
console.log('SPY Counter:', outgoingMessageSpy.callCount);
console.log('SPY CalledOnce:', outgoingMessageSpy.calledOnce);
expect(outgoingMessageSpy.calledOnce).to.be.true;
});
});
When I run using ts-mocha from terminal, the result is like this:
$ npx ts-mocha get.spec.ts
GET
Get start
Get finish
SPY Counter: 1
SPY CalledOnce: true
✓ should call end with the correct value (204ms)
1 passing (206ms)
You see that you have setup the spy to OutgoingMessage.prototype.end correctly.
BUT, when you remove await inside the test (see NOTE inside get.spec.ts file), this is the result:
$ npx ts-mocha get.spec.ts
GET
Get start
SPY Counter: 0
SPY CalledOnce: false
1) should call end with the correct value
0 passing (8ms)
1 failing
1) GET
should call end with the correct value:
AssertionError: expected false to be true
+ expected - actual
-false
+true
Get finish
The condition is: end method get called correctly but after the it test has been evaluated.

shopify-api-node: promise not returning value

Setting up a node.js app to retrieve order(s) information from shopify. I'm trying to store that data and run some logic, but I can't seem to get a hold of it.
THIS WORKS:
shopify.order.list()
.then(function(orders){
console.log(orders);
});
THIS DOES NOT WORK:
var orders_json;
shopify.order.list()
.then(function(orders){
orders_json=orders;
//console.log(orders);
});
console.log(orders_json); //returns undefined
Let me introduce you to the world of async/await. As long as you declare your function as async and the function you are "awaiting" returns a promise, you can handle this in a more synchronous way. Have a look at the docs linked above. Notice how I called the async function after it was declared. You can't call await outside the scope of an async function.
async function fetchOrders() {
try {
const orders_json = await shopify.order.list();
// do stuff with orders_json
return orders_json;
} catch(err) {
// handle err
}
}
const orders = fetchOrders();

Resources