JavaScript - mocking console in Jest / mock "was not called" - javascript

I'm trying to mock console.info which I know will be called when an imported function runs. The function consists entirely of a single fetch which, when not running in production, reports the request and response using console.info.
At the question Jest. How to mock console when it is used by a third-party-library?, the top-rated answer suggests overwriting global.console, so I'm using jest.spyOn to try that out:
import * as ourModule from "../src/ourModule";
test("Thing", () => {
// Tested function requires this. Including it here in case it's causing
// something quirky that readers of this question may know about
global.fetch = require("jest-fetch-mock");
const mockInfo = jest.spyOn(global.console, "info").mockImplementation(
() => { console.error("mockInfo") }
);
ourModule.functionBeingTested("test");
expect(mockInfo).toHaveBeenCalled();
}
As expected, the output contains an instance of "mockInfo". However, then testing that with toHaveBeenCalled() fails.
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
40 |
41 | ourModule.functionBeingTested("test");
> 42 | expect(mockInfo).toHaveBeenCalled();
| ^
43 |
at Object.toHaveBeenCalled (__tests__/basic.test.js:42:22)
console.error __tests__/basic.test.js:38
mockInfo
I've tried moving the spyOn to before the module is loaded, as suggested in one of the comments on the answer, with no difference in result. What am I missing here?
Here's the function in question:
function functionBeingTested(value) {
const fetchData = {
something: value
};
fetch("https://example.com/api", {
method: "POST",
mode: "cors",
body: JSON.stringify(fetchData),
})
.then( response => {
if (response.ok) {
if (MODE != "production") {
console.info(fetchData);
console.info(response);
}
} else {
console.error(`${response.status}: ${response.statusText}`);
}
})
.catch( error => {
console.error(error);
});
}

Issue
console.info is called in a Promise callback which hasn't executed by the time ourModule.functionBeingTested returns and the expect runs.
Solution
Make sure the Promise callback that calls console.info has run before running the expect.
The easiest way to do that is to return the Promise from ourModule.functionBeingTested:
function functionBeingTested(value) {
const fetchData = {
something: value
};
return fetch("https://example.com/api", { // return the Promise
method: "POST",
mode: "cors",
body: JSON.stringify(fetchData),
})
.then(response => {
if (response.ok) {
if (MODE != "production") {
console.info(fetchData);
console.info(response);
}
} else {
console.error(`${response.status}: ${response.statusText}`);
}
})
.catch(error => {
console.error(error);
});
}
...and wait for it to resolve before asserting:
test("Thing", async () => { // use an async test function...
// Tested function requires this. Including it here in case it's causing
// something quirky that readers of this question may know about
global.fetch = require("jest-fetch-mock");
const mockInfo = jest.spyOn(global.console, "info").mockImplementation(
() => { console.error("mockInfo") }
);
await ourModule.functionBeingTested("test"); // ...and wait for the Promise to resolve
expect(mockInfo).toHaveBeenCalled(); // SUCCESS
});

Related

Mocha Unit Test Timeout of 10000ms exceeded Error when using await Promise.all

File: index.ts // Class Based Default exported
getItemsA() {
return Promise.resolve({ // Api Call in real scenario. Mocking here for now.
success: true;
result: [{
itemA: []
}]
});
}
getItemsB() {
return Promise.resolve({ // Api Call in real scenario. Mocking here for now.
success: true;
result: [{
itemB: []
}]
});
}
File service.ts
import { getItemsA, getItemsB } from 'index.ts';
getService() {
return new Promise(async (resolve, reject) => {
const parallelApis = await Promise.all([ // UT Stucks here. Not able to get the parallelApis response.
getItemsA(), getItemsB()
]);
.... other mapping
resolve({
.......
});
});
}
File test.ts
import items from 'index.ts'; // Class based. Default exported
import service from 'service.ts'; // Class based. Default exported
import { expect } from "chai";
import * as sinon from "sinon";
describe.only("Service", () => {
let itemAStub;
let itemBStub;
beforeEach(() => {
itemAStub = sinon.stub(items, "getItemsA");
itemBStub = sinon.stub(items, "getItemsB");
itemAStub.callsFake(() => {
return Promise.resolve({
body: myMock // Dummy
});
});
itemBStub.callsFake(() => {
return Promise.resolve({
body: someOtherMock // Dummy
});
});
});
afterEach(() => {
itemAStub.restore();
itemBStub.restore();
});
it("Should filter valid gift options", (done) => {
service.getService().then(res => {
console.log("RESPONSEEEEEE", res);
expect(res).to.deep.equal(myMockResponse);
done();
})
.catch(err => {
console.log("Errorr");
done(err);
});
});
});
Can somebody help me to identify the issue when i tried to run the test case I am getting the below error.
Error: Timeout of 10000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I know that extending the timeout is not a solution to fix the issue. Stub is created, but not sure why it is getting timeout error when code reaches await Promise.all. If I remove done() then test case will succeed but then function is not getting executed. Any help would be really appreciated.
When we run Mocha from the command line, it will read this file which has a default timeout.
To change this time you can reset using:
./node_modules/.bin/mocha --timeout 20000

Converting jQuery ajax to fetch

I have this piece of code that calls a function getTableData and expects a Promise in return.
function populateTableRows(url) {
successCallback = () => { ... };
errorCallback = () => { ... };
getTableData(url, successCallback, errorCallback).then(tableData => {
// do stuff with tableData
}
}
This is used in many places across my codebase, and I'm looking to keep the behavior the same as I move away from using jQuery's ajax (and jQuery in general)
In getTableData, I'm currently using $.ajax like so
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
const ajaxOptions = {
type: 'POST',
url: url,
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
data: { // some data }
};
return $.ajax(ajaxOptions).done(successCallback).fail(errorCallback);
}
This currently returns a Promise for successful requests. For bad requests where fail is invoked, it doesn't appear that a Promise is returned and the then doesn't run in the calling function (which is okay in this case).
When converting the request over to use fetch, I have something like this
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials: 'include',
body: { // some data }
})
.then(response => {
let json = response.json();
if (response.status >= 200 && response.status < 300) {
successCallback(json);
return json;
} else {
return json.then(error => {throw error;});
}
}).catch((error) => {
errorCallback(error);
return
});
Successful requests appear to be behaving similarly to the ajax code that I currently have, but now the then callback is running for bad requests which is causing errors in my code.
Is there a way with fetch to mimic the fail behavior of jQuery where the Promise is seemingly aborted for bad requests? I'm fairly new to using Promises and after some experimentation/searching I haven't been able to come up with a solution.
When you .catch() in a chain of promises, it means you already handled the error, and subsequent .then() calls continue successfully.
For example:
apiCall()
.catch((error) => {
console.log(error);
return true; // error handled, returning true here means the promise chain can continue
})
.then(() => {
console.log('still executing if the API call fails');
});
What you want, in your case, is when you handle the error with the callback, to continue to throw it so the promise chain is broken. The chain then further needs a new .catch() block to handle the new error.
apiCall()
.catch((error) => {
console.log(error); // "handled", but we're still not done
throw error; // instead of returning true, we throw the error further
// 👆 this can also be written as `return Promise.reject(error);`
})
.then(() => {
console.log('not executing anymore if the API call fails');
})
.catch((error) => {
// handle the same error we have thrown from the previous catch block
return true; // not throwing anymore, so error is handled
})
.then(() => {
console.log('always executing, since we returned true in the last catch block');
});
By the way, what you return from one then/catch block, the following one will get it as a param.
apiCall()
.then((response) => {
/* do something with response */;
return 1;
})
.catch((error) => { return 'a'; })
.then((x) => console.log(x)) // x is 'a' if there's an error in the API call, or `1` otherwise
In your .catch you implicitly return undefined and thus "handle" the error. The result is a new Promise that fulfills to undefined.
.catch((error) => {
errorCallback(error);
return Promise.reject();
});
should be enough to keep the returned Promise rejecting.
Or you assign the intermediate Promise to a var and return that, and not the result to the fail handling:
var reqPromise = fetch(url, {
// ...
})
.then(response => {
// ...
return json.then(error => {throw error;});
});
reqPromise.catch((error) => {
errorCallback(error);
return
});
return reqPromise;

Promise is resolved to undefined

I am sorry if it is simple question, I'm new to javascript
So I have simple axios GET request. It is used three times in my code, so I thought that I could make it an external function, to avoid duplicating code, to making it cleaner and easy readable
The problem is when I call to that function, return value is undefined. And this is because code is working like synchronous. So I thought that I need to make the function return a Promise, and in function call I have to use async/await or then syntax to get the response in the right time. But after many tries code is still running as synchronous
I read a lot of theory on promises and how they work, got a solid understanding of when they change states, but something goes wrong when I try to implement them on my own
Here is the function that retrieves data
const getMessagesFromChat = async (JWT_header, chatId) => {
if (JWT_header !== '') {
//1 let messages
//4 return
axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
})
.then(response => {
console.log('messages (fetch)', response.data)
//1 messages = response.data
//1 return messages
return response.data //2
//3 return Promise.resolve(response.data)
})
.catch(error => {
console.log(error, error.response)
})
//5 return Promise.resolve(messages)
}
}
I marked comments with numbers based on what I've tried
make a variable and return it in then block
make function async so everything it returns is wrapped in promise
return explicit promise using Promise.resolve() in then block
return the whole request as a promise
return explicit promise using Promise.resolve() after the request
All responses except 4 were undefined. In 4 variant log statement shows the promise object itself instead of promise value. In other variants log statement shows first `undefined` response and then the actual response from request
I tried to implement second advice from
https://stackoverflow.com/questions/45620694/how-to-return-response-of-axios-in-return/45622080
but it did not work.
This is the function I tried to use to get the result (onClick)##
const selectChat = () => {
const JWT_header = getToken()
if (JWT_header !== null) {
try {
const messages = await getMessagesFromChat(JWT_header, chatId)
console.log('messages (after fetch)', messages)
//setMessages(messages)
} catch (error) {
console.log(error)
}
} else {
setIsLoggedIn(false)
}
or
const selectChat = () => {
const JWT_header = getToken()
if (JWT_header !== null) {
getMessagesFromChat(JWT_header, chatId)
.then(response => {
console.log(response)
setMessages(response)
})
.catch (error =>console.log(error))
} else {
setIsLoggedIn(false)
}
But none of them worked as expected
What am I doing wrong?
Your function doesn't return anything. A return statement in a then callback is not sufficient, you'd still have to return the promise chain itself from the outer function.
Use either .then() syntax:
function getMessagesFromChat(JWT_header, chatId) { // no `async` here
if (JWT_header !== '') {
return axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
// ^^^^^^
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
}).then(response => {
console.log('messages (fetch)', response.data)
return response.data
})
} else {
return Promise.resolve(undefined)
// ^^^^^^ important for chaining
}
}
or async/await syntax:
async function getMessagesFromChat(JWT_header, chatId) {
if (JWT_header !== '') {
const respnse = await axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
// ^^^^^
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
});
console.log('messages (fetch)', response.data)
return response.data
}
// else return undefined
}

How to raise a Timeout Error in node.js if code takes long time to finish?

in ruby I can:
require 'timeout'
Timeout.timeout 10 do
# do smth > 10 seconds
end
it will raise timeout error to avoid code lock, how to do same thing in nodejs, nodejs #setTimeout doesn't fit my need
one case is, when i http.get timeout(for ex, netowrk is unstable), I should set timeout and handle the failed get request, I hope impl #timeout, how should i do?
try {
timeout(10, function () {
http.get("example.com/prpr")
})
} catch (e) {
if (e.message == "timeout") {
// do smth
} else {
throw e
}
}
You could look into a Promise-based approach here.
Using promises you can pass a function to be executed, and then the standard catch is called if that function raises an exception.
There is a helpful promise-based timeout library on NPM (npm install promise-timeout request-promise), and you could use it in Node something along the lines of...
'use strict';
var promiseTimeout = require('promise-timeout');
var requestPromise = require('request-promise');
promiseTimeout.timeout(requestPromise("http://example.com/prpr"), 10000)
.then(function (result) {
console.log({result});
}).catch(function (err) {
if (err instanceof pt.TimeoutError) {
console.error('HTTP get timed out');
}
});
I had a similar situation with nestJS based on node.js.
When calling an external API, it was a problem that even my service slowed down if it took too long. (If the external api is delayed, my service also had a problem of waiting forever.)
I figured out 2 ways.
First way:
const result = await axios({
timeout: 10000, // error: [AxiosError: timeout of 10000ms exceeded] { code: 'ECONNABORTED', ...
...
});
Second way: Promise.race()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
// first function
const callAPI = axios({
method: "GET",
url: "http://yourapi",
headers: {
...
}
});
// second function
const timeoutCheck = (s) => {
return new Promise(resolve => setTimeout(resolve, s));
}
// check delay (first function VS second function)
const result = await Promise.race([
callAPI,
timeoutCheck(10000).then(() => {
throw new Error("api not responding for more than 10 seconds");
}),
]);
const { data: { resultCode, resultData } } = result;
You can try this out in your case:
var request = http.get(options, function (res) {
// other code goes here
});
request.setTimeout( 10000, function( ) {
// handle timeout here
});

testing promises causing undefined values

I am getting this error when I am testing my code:
1) Sourcerer Testing: getStatusCode :
Error: Expected undefined to equal 200
I'm not sure why I am getting undefined in my tests but when I run the code I get 200. It might be from not handling promises properly
Test code:
import expect from 'expect';
import rp from 'request-promise';
import Sourcerer from './sourcerer';
describe("Sourcerer Testing: ", () => {
let sourcerer = new Sourcerer(null);
const testCases = {
"https://www.google.com": 200,
// "www.google.com":
};
describe("getStatusCode", () => {
it("", () => {
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
expect(sourcerer.url).toEqual(testCase);
expect(sourcerer.getStatusCode()).toEqual(testCases[testCase]);
}
});
});
});
code:
import rp from 'request-promise';
export default class Sourcerer {
constructor(url) {
this.options = {
method: 'GET',
url,
resolveWithFullResponse: true
};
this.payload = {};
}
setSourcererUrl(url) {
this.url = url;
}
getSourcererUrl() {
return this.url;
}
analyzeSourcePage() {
rp(this.options).then((res) => {
console.log(res);
}).catch((err) => {
console.log("ERROR");
throw(err);
});
}
getStatusCode() {
rp(this.options).then((res) => {
console.log(res.statusCode);
return res.statusCode;
}).catch((err) => {
console.log("STATUS CODE ERROR");
return 0;
});
}
}
getStatusCode doesn't return anything. And it should return a promise:
getStatusCode() {
return rp(this.options)...
}
The spec will fail in this case, because it expects promise object to equal 200.
It is even more complicated because the spec is async and there are several promises that should be waited before the spec will be completed. It should be something like
it("", () => {
let promises = [];
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
let statusCodePromise = sourcerer.getStatusCode()
.then((statusCode) => {
expect(sourcerer.url).toEqual(testCase);
expect(statusCode).toEqual(testCases[testCase]);
})
.catch((err) => {
throw err;
});
promises.push(statusCodePromise);
}
return promises;
});
co offers an awesome alternative to Promise.all for flow control:
it("", co.wrap(function* () {
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
expect(sourcerer.url).toEqual(testCase);
let statusCode = yield sourcerer.getStatusCode();
expect(statusCode).toEqual(testCases[testCase]);
}
});
Disclaimer: I wouldn't run a for-loop in a single it(), since I want to know which iteration failed. granted that there are ways to achieve that, but that is another story. Also, this very much depends on you test runner, but here is some rules of thumb I find useful.
But for what you have asked, the test should not evaluate until the promise is resolved. sometimes (e.g. in mocha), that means returning the promise from the it() internal function. sometimes, it means getting a done function and calling it when you are ready for the test to evaluate. If you provide more info on your test framework, I may be able to help (others certainly would be)

Categories

Resources