After I have successfully implemented one of methods to fetch some data and run test
Code:
const fetchData = async (url) => {
const response = await axios.get(url);
const contentType = response.headers['content-type'];
if (typeof response.data === 'object') {
return JSON.stringify(response.data);
}
throw new Error('Content-Type is unrecognized');
};
module.exports = fetchData;
And test:
describe('fetchData', () => {
it('should return json string response data on successful request', async () => {
const responseData = await fetchData(url);
const expectedData = JSON.stringify({ key1: 'value1' });
assert.deepEqual(responseData, expectedData, 'Response data doesn\'t match');
});
However, I wanted to implement scheduling to my method. I implemented in by using node-scheduler npm module.
After my modification
scheduler.scheduleJob({ start: startTime, end: endtTime }, async () => {
const fetchData = async (url) => {
const response = await axios.get(url);
}
Tests are failing immadietly, furthermore I noticed that error log is going continuously, therefore I have to kill test.
Does anyone have an idea why adding simple scheduler makes my error not working? I am using:
Node v.8.11.4
chai-as-promised
nock
Related
Hi guys I'm having trouble testing the below JS using Jest. It starts with waitForWorker. if the response is 'working' then it calls waitForWorker() again. I tried Jest testing but I don't know how to test an inner function call and I've been researching and failing.
const $ = require('jquery')
const axios = require('axios')
let workerComplete = () => {
window.location.reload()
}
async function checkWorkerStatus() {
const worker_id = $(".worker-waiter").data('worker-id')
const response = await axios.get(`/v1/workers/${worker_id}`)
return response.data
}
function waitForWorker() {
if (!$('.worker-waiter').length) {
return
}
checkWorkerStatus().then(data => {
// delay next action by 1 second e.g. calling api again
return new Promise(resolve => setTimeout(() => resolve(data), 1000));
}).then(worker_response => {
const working_statuses = ['queued', 'working']
if (worker_response && working_statuses.includes(worker_response.status)) {
waitForWorker()
} else {
workerComplete()
}
})
}
export {
waitForWorker,
checkWorkerStatus,
workerComplete
}
if (process.env.NODE_ENV !== 'test') $(waitForWorker)
Some of my test is below since i can't double check with anyone. I don't know if calling await Worker.checkWorkerStatus() twice in the tests is the best way since waitForWorker should call it again if the response data.status is 'working'
import axios from 'axios'
import * as Worker from 'worker_waiter'
jest.mock('axios')
beforeAll(() => {
Object.defineProperty(window, 'location', {
value: { reload: jest.fn() }
})
});
beforeEach(() => jest.resetAllMocks() )
afterEach(() => {
jest.restoreAllMocks();
});
describe('worker is complete after 2 API calls a', () => {
const worker_id = Math.random().toString(36).slice(-5) // random string
beforeEach(() => {
axios.get
.mockResolvedValueOnce({ data: { status: 'working' } })
.mockResolvedValueOnce({ data: { status: 'complete' } })
jest.spyOn(Worker, 'waitForWorker')
jest.spyOn(Worker, 'checkWorkerStatus')
document.body.innerHTML = `<div class="worker-waiter" data-worker-id="${worker_id}"></div>`
})
it('polls the correct endpoint twice a', async() => {
const endpoint = `/v1/workers/${worker_id}`
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint]])
expect(data).toMatchObject({"status": "working"})
})
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint],[endpoint]])
expect(data).toMatchObject({"status": "complete"})
})
})
it('polls the correct endpoint twice b', async() => {
jest.mock('waitForWorker', () => {
expect(Worker.checkWorkerStatus).toBeCalled()
})
expect(Worker.waitForWorker).toHaveBeenCalledTimes(2)
await Worker.waitForWorker()
})
I think there are a couple things you can do here.
Inject status handlers
You could make the waitForWorker dependencies and side effects more explicit by injecting them into the function this lets you fully black box the system under test and assert the proper injected effects are triggered. This is known as dependency injection.
function waitForWorker(onComplete, onBusy) {
// instead of calling waitForWorker call onBusy.
// instead of calling workerComplete call onComplete.
}
Now to test, you really just need to create mock functions.
const onComplete = jest.fn();
const onBusy = jest.fn();
And assert that those are being called in the way you expect. This function is also async so you need to make sure your jest test is aware of the completion. I notice you are using async in your test, but your current function doesnt return a pending promise so the test will complete synchronously.
Return a promise
You could just return a promise and test for its competition. Right now the promise you have is not exposed outside of waitForWorker.
async function waitForWorker() {
let result = { status: 'empty' };
if (!$('.worker-waiter').length) {
return result;
}
try {
const working_statuses = ['queued', 'working'];
const data = await checkWorkerStatus();
if (data && working_statuses.includes(data.status)) {
await waitForWorker();
} else {
result = { status: 'complete' };
}
} catch (e) {
result = { status: 'error' };
}
return result;
}
The above example converts your function to async for readability and removes side effects. I returned an async result with a status, this is usefull since there are many branches that waitForWorker can complete. This will tell you that given your axios setup that the promise will complete eventually with some status. You can then use coverage reports to make sure the branches you care about were executed without worrying about testing inner implementation details.
If you do want to test inner implementation details, you may want to incorporate some of the injection principals I mentioned above.
async function waitForWorker(request) {
// ...
try {
const working_statuses = ['queued', 'working'];
const data = await request();
} catch (e) {
// ...
}
// ...
}
You can then inject any function into this, even a mock and make sure its called the way you want without having to mock up axios. In your application you simply just inject checkWorkerStatus.
const result = await waitForWorker(checkWorkerStatus);
if (result.status === 'complete') {
workerComplete();
}
I have my Axios code, which should download a file (cribbed and changed from here).
'use strict'
const Fs = require('fs')
const Path = require('path')
const axios = require('axios')
const URL = 'https://unsplash.com/photos/AaEQmoufHLk/download?force=true'
async function downloadMtgJson() {
const path = Path.resolve(__dirname, 'resources', 'code.jpg')
const writer = Fs.createWriteStream(path)
const response = await axios({
URL,
method: 'GET',
responseType: 'stream'
})
console.log(response.data.pipe)
if (response && response.data) response.data.pipe(writer);
console.log('hello')
return handleWriter(writer)
}
const handleWriter = (writer) => new Promise((resolve, reject) => {
writer.on('finish', resolve)
writer.on('error', reject)
})
module.exports = {
downloadMtgJson,
handleWriter,
URL
}
And I looked for an example of axios tests for this online, and some SO questions, and found some code I've also tweaked to my purposes:
const axios = require('axios');
const { downloadMtgJson, URL } = require('./resources');
jest.mock('axios');
describe.only('fetchData', () => {
it('fetches successfully data from an URL', async () => {
expect.assertions(1);
const data = { status: 200, data: { pipe: () => 'data' } };
axios.mockImplementationOnce(() => Promise.resolve(data));
console.log('waiting...')
await expect(downloadMtgJson()).resolves.toEqual('data');
console.log('waited')
expect(axios).toHaveBeenCalledWith(
`${URL}/search?query=react`,
);
});
});
The issue I am having, is that the promise part of the handleWriter seems to be timing out. At one point I mocked it in the same way as I mocked axios, but it didn't make a difference.
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
6 |
7 | describe.only('fetchData', () => {
> 8 | it('fetches successfully data from an URL', async () => {
I can't figure out what is different in what I've done, vs what I've seen in testing examples.
What can I do to get my tests working?
This line is also asynchronous:
const writer = Fs.createWriteStream(path)
And needs mocking out to prevent the error you see.
The full test code is now:
jest.mock('fs');
jest.mock('axios');
jest.mock('./resource.util')
// ... other code ...
it('fetches successfully data from an URL', async () => {
const data = { status: 200, data: { pipe: () => 'data' } };
axios.mockImplementationOnce(() => data);
fs.createWriteStream.mockImplementationOnce(() => 'fs');
handleWriter.mockImplementationOnce(() => 'data'); //handle writer was extracted to a util file
const expectedJson = await downloadMtgJson();
expect(expectedJson).toEqual('data');
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({ URL: 'https://unsplash.com/photos/AaEQmoufHLk/download?force=true' }),
);
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({ responseType: 'stream' }),
);
});
I have an async lambda, which performs an async SQS sendMessage request. The SQS queue is a standard queue, not FIFO, just to clarify.
Here's an example of code (without irrelevant part of the logic):
exports.functionHandler = async (event, context, callback) => {
try {
let parsedBody = JSON.parse(event.Records[0].body);
let modifiedBody = await doStuff(parsedBody);
let sqsPayload = {
MessageBody: JSON.stringify(modifiedBody),
QueueUrl: my-queue-url
};
await sqs.sendMessage(sqsPayload).promise();
callback(null, utils.respondSuccess("Done"));
} catch (err) {
// Handle error
callback(null, utils.respondError(err));
}
};
const doStuff = async payload => {
// Do stuff
}
Pretty simple.
Now the problem: I'm trying to test this function using the package aws-sdk-mock. This is how I was stubbing the sendMessage function when the lambda wasn't async and the sendMessage function was using the callback:
it("an awesome title for my test", async () => {
let payload = {
Records: [
// Data here
]
};
AWS.mock("SQS", "sendMessage", (param, callback) => {
let response = {
ResponseMetadata: {
RequestId: "test-request-id"
},
MD5OfMessageBody: "a892e8d8589e97ca92fb70020f01c16c",
MessageId: "test-message-id"
};
callback(null, response);
});
await app.functionHandler(payload, {}, (err, result) => {
let parsedBody = JSON.parse(result.body);
expect(parsedBody.message).to.be.equal("Done");
// More stuff
});
AWS.restore();
});
If I use this test, the sendMessage function throws the following error:
sendMessage returned an invalid MD5 response. Got "undefined", expecting "a892e8d8589e97ca92fb70020f01c16c".
I'm not sure how to test sendMessage asynchronously. I don't mind adopting a different package if it helps me to get the job done.
Can anyone help?
Thanks a lot
I've not used aws-sdk-mock but apparently in your mock you are using callback and in the lambda handler it is an async call. I use proxyquire for mocking dependencies. Here is an example:
functionHandler.js
Don't need to use callback and context in Lambda runtime Node8.10.
let AWSSQS = require('aws-sdk/clients/sqs');
let sqs = new AWSSQS();
exports.functionHandler = async (event) => {
// No need to use callback when Lambda runtime is 8.10.
try {
let parsedBody = JSON.parse(event.Records[0].body);
let modifiedBody = await doStuff(parsedBody);
let sqsPayload = {
MessageBody: JSON.stringify(modifiedBody),
QueueUrl: my-queue-url
};
await sqs.sendMessage(sqsPayload).promise();
return utils.respondSuccess('Done');
} catch (err) {
throw utils.respondError(err);
}
};
test.spec.js
Pretty much self explanatory. Your define an object with name of dependency as property.
const proxyquire = require('proxyquire');
let app = require('path/to/function');
describe('SQS', () => {
it("an awesome title for my test", async (done) => {
const app = proxyquire(app, {
'aws-sdk/clients/sqs': function() {
this.sendMessage = (params) => {
return {
promise: () => {
return Promise.resolve({
ResponseMetadata: {
RequestId: 'test-request-id'
},
MD5OfMessageBody: 'a892e8d8589e97ca92fb70020f01c16c',
MessageId: 'test-message-id'
});
}
}
}
}
});
let payload = {
Records: [
// Data here
]
};
const data = await app.functionHandler(payload);
let parsedBody = JSON.parse(data.body);
expect(parsedBody.message).to.be.equal("Done");
done();
});
});
I want to mock the result of a function within a node module so that i can run assertions.
Considering the following node module:
const doPostRequest = require('./doPostRequest.js').doPostRequest;
const normalizeSucessResult = require('./normalizer.js').normalizeSucessResult;
const normalizeErrorResult = require('./normalizer.js').normalizeErrorResult;
exports.doPost = (params, postData) => {
return doPostRequest(params, postData).then((res) => {
const normalizedSuccessResult = normalizeSucessResult(res);
return normalizedSuccessResult;
}).catch((err) => {
const normalizedErrorResult = normalizeErrorResult(err);
return normalizedErrorResult;
})
}
The function doPostRequest returns a promise. How can i fake the return value of this promise so that i can assert if normalizeSucessResult has been called?
So for i have tried:
const normalizeSucessResult = require('./normalizer.js');
const doPostRequest = require('./doPostRequests.js');
const doPost = require('./doPost.js');
it('runs a happy flow scenario', async () => {
let normalizeSucessResultStub = sinon.stub(normalizeSucessResult, 'normalizeSucessResult');
let postData = { body: 'Lorum ipsum' };
let params = { host: 'someUrl', port: 433, method: 'POST', path: '/' };
sinon.stub(doPostRequest, 'doPostRequest').resolves("some response data"); //Fake response from doPostRequest
return doPost.doPost(params, postData).then((res) => { //res should be equal to some response data
expect(normalizeSucessResultStub).to.have.been.calledOnce;
expect(normalizeSucessResultStub).to.have.been.with("some response data");
});
});
The doPostRequest module looks like this:
const https = require('https')
module.exports.doPostRequest = function (params, postData) {
return new Promise((resolve, reject) => {
const req = https.request(params, (res) => {
let body = []
res.on('data', (chunk) => {
body.push(chunk)
})
res.on('end', () => {
try {
body = JSON.parse(Buffer.concat(body).toString())
} catch (e) {
reject(e)
}
resolve(body)
})
})
req.on('error', (err) => {
reject(err)
})
if (postData) {
req.write(JSON.stringify(postData))
}
req.end()
})
}
You can use Promise.resolve to return a promise with any given value.
Promise.resolve(“hello world”);
For stub your func you need to do like this
sinon.stub({doPostRequest}, 'doPostRequest').resolves("some response data")
Okay, i figured it out. The function doPostRequest was loaded using require, on the top of the file using const doPostRequest = require('./doPostRequest.js').doPostRequest;
In order to mock the data that comes back from a function that is loaded using require i had to use a node module called mock-require. There are more modules that can take care of this (proxyquire is a populair one) but i picked mock-require (i did not have a specific reason for choosing mock-require).
For anyone else that is stuck with a similar problem, try mock-require to mock the respose from files that are loaded using require.
I have a function, startSurvey, which, when run, checks if there are questions in a .json file. If there are no questions, it fetches some questions from Typeform and writes them to the .json file using saveForm. After it writes, I would like to continue executing some code that reads the .json file and logs its contents. Right now, await saveForm() never resolves.
I have promisified the fs.readFile and fs.writeFile functions.
//typeform-getter.js
const fs = require('fs')
const util = require('util')
const fetch = require('cross-fetch')
require('dotenv').config()
const conf = require('../../private/conf.json')
const typeformToken = conf.tokens.typeform
const writeFile = util.promisify(fs.writeFile)
const getForm = async () => {
const form = await fetch(`https://api.typeform.com/forms/${process.env.FORM_ID}`, {
headers: {
"Authorization": `bearer ${typeformToken}`
}
}).then(res => res.json())
const fields = form.fields
return fields
}
const saveForm = async () => {
const form = await getForm()
return writeFile(__dirname + '/../data/questions.json', JSON.stringify(form))
.then((e) => {
if (e) console.error(e)
else console.log('questions saved')
return
})
}
module.exports = saveForm
//controller.js
const fs = require('fs')
const util = require('util')
const request = require('request')
require('dotenv').config()
const typeformGetter = require('./functions/typeform-getter')
const readFile = util.promisify(fs.readFile)
const saveForm = util.promisify(typeformGetter)
let counter = 1
const data = []
const getQuestions = async() => {
console.log('called')
try {
let data = await readFile(__dirname + '/data/questions.json')
data = JSON.parse(data)
return data
} catch (e) {
console.error('error getting questions from read file', e)
}
}
const startSurvey = async (ctx) => {
try {
const questions = await getQuestions()
if (!questions) await saveForm()
console.log(questions) //NEVER LOGS
} catch (error) {
console.error('error: ', error)
}
}
startSurvey() //function called
I don't know your exact error, but there are multiple things wrong with your code:
You're using incorrectly the promisified version of fs.writeFile, if an error occurs, the promise will be rejected, you won't get a resolved promise with an error as the resolved value, which is what you're doing.
Use path.join instead of concatenating paths.
In startSurvey, you're using console.log(questions) but that wont have any data if questions.json doesn't exists, which should happen the first time you run the program, since it's filled by saveForm, so you probably want to return the questions in saveForm
So saveForm should look something like this:
const saveForm = async () => {
const form = await getForm();
const filePath = path.join(path.__dirname, '..', 'data', 'questions.json');
await writeFile(filePath, JSON.stringify(form));
console.log('questions saved');
return form;
}
And startSurvey
const startSurvey = async (ctx) => {
try {
const questions = await getQuestions() || await saveForm();
// This will be logged, unless saveForm rejects
// In your code getQuestions always resolves
console.log(questions);
} catch (error) {
console.error('error: ', error)
}
}
In your controller.js you're using util.promisify on saveForm when it is already a promise.
So it should be:
const saveForm = require('./functions/typeform-getter')