sinon spy doesn't register call in a generator loop? - javascript

I want to check that a piece of code is being called, so I'm using a sinon spy to assert this. However, the spy seems to be failing, despite console.logs showing that the code has been called correctly.
I'm wondering if my function being a generator is causing my spy to misreport what it's doing.
my code (i've taken out some chunks for brevity):
isBlacklisted(release, jobUUID) {
names.forEach((name) => {
this._spawnPythonProcessGenerator(
this.IS_BLACKLISTED_SCRIPT,
name
).next().value
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
}
_errorEvent(release, name, err, jobUUID) {
console.log('got here');
}
*_spawnPythonProcessGenerator(scriptSrc, name) {
const pythonProcess = this._childProcess.spawn(
'python3',
[...arguments]
);
yield new Promise((resolve, reject) => {
pythonProcess.stderr.on('data', (err) => {
reject(err.toString());
});
pythonProcess.stdout.on('data', (data) => {
resolve(data.toString());
});
});
}
and my tests:
const Blacklist = require('../../src/Blacklist2');
const childProcess = require('child_process');
const uuid = require('uuid/v4');
describe('Blacklist', () => {
let blacklist;
beforeEach(() => {
blacklist = new Blacklist(childProcess);
blacklist.IS_BLACKLISTED_SCRIPT = './test/helpers/good.py';
});
describe('isBlacklisted', () => {
it('should call the _errorEvent for every name in a release when the blacklist application is not available', async () => {
let release = {
id: 1001,
asset_controller: {
id: 54321,
},
display_name: 'Blah',
names: [
{
id: 2001,
name: 'Blah',
},
],
};
blacklist.IS_BLACKLISTED_SCRIPT = './test/helpers/'+ uuid() +'.py';
const spy = sinon.spy(blacklist, '_errorEvent');
blacklist.isBlacklisted(release, uuid());
console.log(spy);
sinon.assert.calledTwice(spy);
spy.restore();
});
});
});
my spy reports:
notCalled: true

I'll expand my comment into an actual answer, hopefully that helps.
Your problem lies with asynchrony, not with the generator. You need isBlacklisted to return a promise you can wait on. Otherwise your assertion happens before the spy is called.
Something like this:
isBlacklisted(release, jobUUID) {
let promises = names.map((name) => {
return this._spawnPythonProcessGenerator(
this.IS_BLACKLISTED_SCRIPT,
name
).next().value
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
return Promise.all(promises);
}
Then, in your test:
return blacklist.isBlacklisted(release, uuid())
.then(() => {
sinon.assert.calledTwice(spy);
});
Also... This isn't related to your problem, but your _spawnPythonProcessGenerator method doesn't need to be a generator. You're only using the first value of it by calling next like that and calling the whole thing over again for each array item.
It will work the same if you take out the *, change yield to return, and skip the .next().value when you call it. You also probably want to rename it because it's not a generator.
_spawnPythonProcess(scriptSrc, name) {
const pythonProcess = this._childProcess.spawn(
'python3',
[...arguments]
);
return new Promise((resolve, reject) => {
pythonProcess.stderr.on('data', (err) => {
reject(err.toString());
});
pythonProcess.stdout.on('data', (data) => {
resolve(data.toString());
});
});
}
When you call it:
let promises = names.map((name) => {
return this._spawnPythonProcess(
this.IS_BLACKLISTED_SCRIPT,
name
)
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
return Promise.all(promises);

Related

Can't send request in componentDidMount React Native

I have an issue with sending a request to backend from my componentDidMount(). Basically I need to do two things before rendering screen:
Obtain data from API call and save it to state
Send that obtained data to backend and take response values from backend.
The problem I've faced on first step is that setState() is async, and even though my array is not empty (I see it's elements in render() and componentDidUpdate fucntion) in componentDidMount() when I console.log() array it will be empty. Now, the issue is: I still need to send that state array to backend before showing the screen. But how can I do it, when it appears empty there?
I have everything working fine if I send the request from the Button element in my render function, but that's not exactly what I need. Any suggestions?
this.state = {
ActivityItem: [],
}
componentDidMount() {
this.getDataFromKit(INTERVAL); //get data from library that does API calls
this.sendDataToServer(); //sending to backend
}
componentDidUpdate() {
console.log("componentDidUpdate ", this.state.ActivityItem) // here array is not empty
}
getDataFromKit(dateFrom) {
new Promise((resolve) => {
AppleKit.getSamples(dateFrom, (err, results) => {
if (err) {
return resolve([]);
}
const newData = results.map(item => {
return { ...item, name: "ItemAmount" };
});
this.setState({ ActivityItem: [...this.state.ActivityItem, ...newData] })
})
});
And last one:
sendDataToServer() {
UserService.sendActivityData(this.state.ActivityItem).then(response => {
}).catch(error => {
console.log(error.response);
})
And here it works as expected:
<Button
title='send data!'
onPress={() => this.sendDataToServer()
} />
UPDATE
If I have like this (wrapped inside initKit function this will return undefined.
AppleKit.initKit(KitPermissions.uploadBasicKitData(), (err, results) => {
if (err) {
return;
}
return new Promise((resolve) => {
AppleKit.getSamples(dateFrom, (err, results) => {
if (err) return resolve([]);//rest is the same
you have to wait for the promise to resolve. You need something like this:
componentDidMount() {
this.getDataFromKit(INTERVAL).then(result => {
this.sendDataToServer(result); //sending to backend
}).catch(e => console.error);
}
and you can update your other function that fetches data to return it:
getDataFromKit(dateFrom) {
return new Promise((resolve) => {
AppleKit.getSamples(dateFrom, (err, results) => {
if (err) return resolve([]);
const newData = results.map(item => {
return { ...item, name: "ItemAmount" };
});
const allData = [ ...this.state.ActivityItem, ...newData ];
this.setState({ ActivityItem: allData });
resolve(allData);
});
});
}
finally, you need the 'sendData' function to not depend on state, but get a param passed to it instead:
sendDataToServer(data) {
UserService.sendActivityData(data).then(response => {
// ... do response stuff
}).catch(error => {
console.log(error.response);
});
}
Handling Multiple Requests
if the requests don't depend on each other:
componentDidMount() {
Promise.all([
promise1,
promise2,
promise3,
]).then(([ response1, response2, response3 ]) => {
// do stuff with your data
}).catch(e => console.error);
}
if the requests do depend on each other:
componentDidMount() {
let response1;
let response2;
let response3;
promise1().then(r => {
response1 = r;
return promise2(response1);
}).then(r => {
response2 = r;
return promise3(response2);
}).then(r => {
response3 = r;
// do stuff with response1, response2, and response3
}).catch(e => console.error);
}
as far as your update, it seems like you wrapped your async request in another async request. I'd just chain it instead of wrapping it:
make the initKit a function that returns a promise
function initKit() {
return new Promise((resolve, reject) => {
AppleKit.initKit(
KitPermissions.uploadBasicKitData(),
(err, results) => {
if (err) reject({ error: 'InitKit failed' });
else resolve({ data: results });
}
);
});
}
make get samples a separate function that returns a promise
function getSamples() {
return new Promise((resolve) => {
AppleKit.getSamples(dateFrom, (err, results) => {
if (err) resolve([]); //rest is the same
else resolve({ data: results });
});
});
}
chain 2 promises back to back: if initKit fails, it will go in the .catch block and getSamples wont run
componentDidMount() {
initKit().then(kit => {
return getSamples();
}).then(samples => {
// do stuff with samples
}).catch(e => console.log);
}

Unit Testing Node JS API using Jest

I'm trying to write unit test cases for NodeJS API by mocking the DB call using jest.fn(() => Promise.resolve(["FAQ 1"]));
My API factory file is something like this:
const getFAQ=(request,reply)=>{
faqs.findByCondition(request.params.filter,success=>{
reply(Response.sendResponse(true, success, ResponseMessages.SUCCESS, StatusCodes.OK));
}, error => {
log.error('ERROR : ', error);
reply(Response.sendResponse(false, error, ResponseMessages.ERROR, 400));
});
};
My Model JS containing findByCondition() is something like this:
findByCondition = (condition, success_callback, error_callback) => {
"use strict";
faq.find(condition, (err, docs) => {
if (err) {
error_callback(err);
} else {
success_callback(docs);
}
});
}
I've tried writing my unit test case as follows:
describe("test cases for FAQ Factory", () => {
utils.callAPI = jest.fn(() => 'test')
test('getFAQ Success Case', (done) => {
const request = {
params: {
filter: 'all'
},
headers: {
authorization: 'asfasfasdfas'
}
}
faqModel.findByCondition = jest.fn(() => Promise.resolve(["FAQ 1"]));
faqFactory.getFAQ(request, (result) => {
expect(result).toBeDefined();
expect(result.status_code).toBe(200);
})
})
});
I'm able to run the test case successfully but the code coverage is not covering the success or error callbacks
but the same test case works if I change my API Factory to something like this:
const getFAQ = (request, reply) => {
faqs.findByCondition(request.params.filter).then(success => {
reply(Response.sendResponse(true, success, ResponseMessages.SUCCESS, StatusCodes.OK));
}).catch(error => {
log.error('ERROR : ', error);
reply(Response.sendResponse(false, error, ResponseMessages.ERROR, 400));
});
};
Is there any way that I could write my test case so that it will cover the success/error callbacks?
Basically you will have to have the findByCondition to be overwritten if you want to test both cases and pass your callback to getFAQ
I've simplified it a little to make it clearer what I mean.
const getFAQ = (request, reply) => {
faqs
.findByCondition(request.params.filter)
.then(success => {
reply(true);
})
.catch(error => {
reply(false);
});
};
const faqs = {
findByCondition: () => {
return Promise.resolve();
}
};
it("works", () => {
jest.spyOn(faqs, `findByCondition`).mockResolvedValue({});
getFAQ({ params: { filter: "hello" } }, reply => {
expect(reply).toBe(true);
});
});
it("doesn't work", () => {
jest.spyOn(faqs, `findByCondition`).mockRejectedValue(new Error(`This fails because of error`));
getFAQ({ params: { filter: "hello" } }, reply => {
expect(reply).toBe(false);
});
});
You can see it here
Update:
I have found a solution by using mockingoose library, writing my test case as follows:
test("test to getFAQ Success", async (done) => {
const request = {
params: {
filter: 'all'
},
headers: {
authorization: testData.authorization
}
}
mockingoose(faqModel.faq).toReturn([], 'find');
const result = await promisify(faqFactory.getFAQ, request);
expect(result).toBeDefined();
expect(result.status).toEqual(200);
done();
})

how to return value from a promise function

I have a function which checks whether a device is online or not. Below is the code.
const ping = require('ping');
export function findDevices(device) {
try {
const hosts = [device];
let result = null;
hosts.forEach((host) => {
ping.promise.probe(host)
.then((res) => {
console.log(res.alive)
result = res.alive;
return {
Status: result
}
});
});
} catch (err) {
logger.error(err, '[ config - findDevices() ]');
console.error(err);
return {
Status: "Failed"
}
}
}
I am calling this function in a redux action like this:
export function start(device) {
return dispatch => {
const status = Connectionstatus.findDevices(device);
return dispatch({
type: actionTypes.CONNECTIONSTATUS,
payload: {
ConnectionStatus: status
}
})
};
}
I am expective the status variable to be either true or false. But i am getting as undefined even though i am returning the value inside then of the promise function. i have tried awaiting this call and still its not working. Any help would be much appreciated. Thanks.
If that's the case you can do like this
const getStatus = async () => {
try {
const hosts = [device];
const promises = [];
hosts.forEach((host) => {
promises.push(ping.promise.probe(host));
});
const result = await Promise.all(promises);
const status = result.map((r) => { return r.alive; });
return status;
} catch (err) {
logger.error(err, '[ config - findDevices() ]');
return { status: 'Failed' };
}
};
Not 100% sure what all the vars are, but have you considered using async/await to simplify things a bit like this?
const getStatus122 = async device => {
return await Promise.all([device].map(ping.promise.probe))
.then(({ alive }) => alive)
.then(Status => ({ Status }))
.catch(error => {
logger.error(error, '[ config - findDevices() ]');
return { Status: 'Failed' };
})
}
More on that here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
With Promises you should inspect the promised result either in when or catch callback functions. With async/await the code may look a bit simpler. Here is the version with explicit promises.
const ping = require('ping');
const Connectionstatus = {
findDevices: (device) => {
return ping.promise.probe(device).then((res) => {
const result = res.alive;
console.log(result);
return {
Status: result,
};
}).catch((err) => {
logger.error(err, '[ config - findDevices() ]');
console.error(err);
return {
Status: "failed"
}
});
}
}
export function start(device) {
return dispatch => {
Connectionstatus.
findDevices(device).
then((status) => {
dispatch({
type: actionTypes.CONNECTIONSTATUS,
payload: {
ConnectionStatus: status
}
})
});
};
}
You may see that error handling moved to the catch callback function while the dispatch is done in the then callback function. And this is the answer to your question.

How to return value from callback in Graphql resolve function?

How do I return the value from callback function and pass it to resolve function in Graphql?
Here's the dummy code to show the concept:
This function runs the sql query:
function runQuery(query, cb){
....
var value = "Something";
cb(null, value);
}
This takes the value from callback function pass it to resolve function in graphql:
function getTitle() {
return runQuery("...", function(err, value){
return value;
});
}
Graphql schema:
var SampleType = new GraphQLObjectType({
name: 'Sample',
fields: () => ({
title: { type: GraphQLString },
}),
});
query: new GraphQLObjectType({
name: 'Query',
fields: () => ({
sample: {
type: SampleType,
resolve: () => getTitle(),
},
}),
}),
You can make use of promises and async to accomplish this.
async function getTitle() {
const queryResult = await runQuery("...");
// ...
// Do post-query stuff here that you currently have in your callback
// ...
return queryResult
}
async function runQuery() {
const value = 'something';
// ...
return value;
}
Node fully supports async/await as of 7.10.0. Use TypeScript or Babel if you're in the browser or are locked into a lower version of node.
Basically you can create a promise around that runQuery so that you can use async await when using the query data
const getTitle = () => {
return new Promise((resolve, reject) => {
runQuery("...", (error, response) => !error
? resolve(response)
: reject(error))
})
}
const asyncFunction = async () => {
const data = await getTitle()
.then((response) => {
// handle response and return what you want
return response.data
})
.catch((error) => {
// handle error, log it, etc, in whatever way you want
console.log(error.message)
return null
})
if(data) { // data is valid
// do what you want with the valid data (no error)
} else { // there was an error
// handle if there is an error
}
}
asyncFunction()

Testing Chained Promises (Jasmine, React, Karma)

I have run into several situations on my present project where I have a chain of promises that I'm not sure how to deal with.
Here is the relevant code block:
return this.axios.get(path, requestOpts)
.then((response) => {console.log('did authorize: ', response); return response})
.then((response) => {
if (response.data.ok) {
window.localStorage.setItem(path, JSON.stringify(response.data));
console.log("Setting localStorage item ", path, response.data);
return response.data.payloadUrl;
} else {
console.error("Non-ok response for ", path, response.data);
const resp: DisplayTokenResponse = response.data;
//TODO: reject promise?
if (resp.status === "AUTHENTICATION_REQUIRED") {
this.axiosService.goToLoginPage(window.location + '');
}
Promise.reject(response.data.message);
}
});
My test (so far) looks like this:
describe('.authorize()', () => {
let axiosSpy: jasmine.Spy;
beforeEach((done) => {
spyOn(svc, 'keyPath').and.returnValue(path);
spyOn(svc, 'storedToken').and.returnValue(stored);
let response = {
data: {
ok: true,
message: 'test-response',
payloadUrl: 'http://payload-url.com'
}
}
spyOn(svc.axios, 'get').and.callFake(
(path:string, reqOpts:AxiosRequestConfig) => {
return new Promise(() => {
response
});
}, (e) => {
console.log(`failed`);
});
});
describe('should authorize user', () => {
it('when supplied a STRING', () => {
clientId = clientId_string;
});
it('when supplied a NUMBER', () => {
clientId = clientId_number;
});
afterEach((done) => {
svc.authorize(clientId, locationId, screenId).then((result) => {
console.log(`result ${result}`);
done();
}, (e) => {
console.log(`failed with error ${e}`);
done();
});
});
});
});
I can test one-level-down promises, but how to I set up my tests to be able to handle situations like this?
Finally got it figured out. I believe it stemmed from a confusion between creating Promise instances versus their resolvers.
The new beforeEach block looks like this:
beforeEach(() => {
spyOn(svc, 'keyPath').and.returnValue(path);
spyOn(svc, 'storedToken').and.returnValue(stored);
let axiosPromise = new Promise((resolve, reject) => {
var responseData = {
data: {
ok: true,
message: 'test-response',
payloadUrl: 'http://payload-url.com'
}
};
resolve(responseData);
});
spyOn(svc.axios, 'get').and.callFake(
()=>{
return axiosPromise;
}
);
});
My tests now pass.

Categories

Resources