i am new to nodejs testing with mocha and chai.right now I am having issue while testing a API route handler with mocha. my route handler code is
exports.imageUpload = (req, res, next) => {
Upload(req,res, async () => {
//get the image files and its original urls (form-data)
let files = req.files['files[]'];
const originalUrls = req.body.orgUrl;
// check the input parameters
if(files == undefined || originalUrls == undefined){
res.status(400).send({status:'failed', message:"input field cannot be undefined"})
}
if(files.length > 0 && originalUrls.length > 0){
//array of promises
let promises = files.map(async(file,index)=>{
//create a image document for each file
let imageDoc = new ImageModel({
croppedImageUrl : file.path,
originalImageUrl: (typeof originalUrls === 'string') ? originalUrls : originalUrls[index],
status: 'New'
});
// return promises to the promises array
return await imageDoc.save();
});
// resolve the promises
try{
const response = await Promise.all(promises);
res.status(200).send({status: 'success', res: response});
}catch(error){
res.status(500).send({status:"failed", error: error})
}
}else{
res.status(400).send({status:'failed', message:"input error"})
}
})
}
the Upload function is just a multer utility to store the imagefile
and my test code is
describe('POST /content/image_upload', () => {
it('should return status 200', async() => {
const response = await chai.request(app).post('/content/image_upload')
.set('Content-Type', 'application/form-data')
.attach('files[]',
fs.readFileSync(path.join(__dirname,'./asset/listEvent.png')), 'asset')
.field('orgUrl', 'https://images.unsplash.com/photo-1556830805-7cec0906aee6?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1534&q=80')
expect(response.error).to.be.false;
expect(response.status).to.be.equal(200);
expect(response.body.status).to.be.equal('success');
response.body.res.forEach(item => {
expect(item).to.have.property('croppedImageUrl');
expect(item).to.have.property('originalImageUrl');
expect(item).to.have.property('status');
expect(item).to.have.property('_id');
})
})
})
and the output shown after running this code is
1) POST /content/image_upload
should return status 200:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/home/techv/content_capture/server/ContentServiceLib/api/test/image_upload.test.js)
I believe this updated code should work
describe('POST /content/image_upload', async() => {
await it('should return status 200', async() => {
const response = await chai.request(app).post('/content/image_upload')
.set('Content-Type', 'application/form-data')
.attach('files[]',
fs.readFileSync(path.join(__dirname,'./asset/listEvent.png')), 'asset')
.field('orgUrl', 'https://images.unsplash.com/photo-1556830805-7cec0906aee6?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1534&q=80')
expect(response.error).to.be.false;
expect(response.status).to.be.equal(200);
expect(response.body.status).to.be.equal('success');
response.body.res.forEach(item => {
expect(item).to.have.property('croppedImageUrl');
expect(item).to.have.property('originalImageUrl');
expect(item).to.have.property('status');
expect(item).to.have.property('_id');
})
})
})
async needs to be added at the top most level as well for it to actually be asynchronous
I think your code is breaking as the upload is taking more then 2 seconds which is the default timeout of mocha as specified in the mocha documentation at https://mochajs.org/#-timeout-ms-t-ms
There are two ways to overcome this problem:
Add a global level timeout while running your command to run the tests --timeout 5s
Add a test specific timeout in the test case itself e.g.
describe('POST /content/image_upload', () => {
it('should return status 200', async() => {
this.timeout(5000) // Timeout
const response = await chai.request(app).post('/content/image_upload')
.set('Content-Type', 'application/form-data')
.attach('files[]',
fs.readFileSync(path.join(__dirname,'./asset/listEvent.png')), 'asset')
.field('orgUrl', 'https://images.unsplash.com/photo-1556830805-7cec0906aee6?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1534&q=80')
expect(response.error).to.be.false;
expect(response.status).to.be.equal(200);
expect(response.body.status).to.be.equal('success');
response.body.res.forEach(item => {
expect(item).to.have.property('croppedImageUrl');
expect(item).to.have.property('originalImageUrl');
expect(item).to.have.property('status');
expect(item).to.have.property('_id');
})
})
})
Just check the line #3 in above code snippet. more details are here : https://mochajs.org/#test-level
Related
I have a test file like this.
const { silBastan } = require("../database.js");
const axios = require('axios').default;
describe("authentication", () => {
describe("when data schema is valid", () => {
test("returns 201 response code if the user doesnt already exists", async () => {
await silBastan();
const response = await axios.post('http://localhost:8000/auth/register', {
email: "my_email",
password: "1234"
});
expect(response.status).toBe(201);
});
});
});
And silBastan is defined here like this
const pg = require("pg");
const client = new pg.Client();
async function silBastan() {
return await client.query(`DELETE FROM account`);
}
Of course i made sure the server started and connected to the database before running the tests.
I wondered if there is something wrong with silBastan and tested it inside a express route handler like this
router.post('/register', async (req, res) => {
const { email, password } = req.body;
await silBastan();
try {
await db.createAccount(email, password);
res.sendStatus(201);
} catch (e) {
res.status(400).json({ err: "Already exists" });
}
});
and there was no timeout. And after this i returned another promise from silBastan like this:
async function silBastan() {
// return await client.query(`DELETE FROM account`);
return new Promise((resolve) => setTimeout(() => resolve(), 1000));
}
And again there is no timeout. I tried couple of other variations as well like these:
function silBastan() {
return client.query(`DELETE FROM account`);
}
async function silBastan() {
await client.query(`DELETE FROM account`);
}
Nothing worked i always get this message:
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
I don't think the problem is with the function because i would get the same behavior in the route handler too.
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();
}
We are creating a node app, based on express, to read from a static local file and return the JSON within the file.
Here is our json-file.js with our route method:
const readFilePromise = require('fs-readfile-promise');
module.exports = {
readJsonFile: async (req, res) => {
try {
const filePath = 'somefile.json';
const file = await readFilePromise(filePath, 'utf8');
res.send(file);
} catch(e) {
res.status(500).json(e);
}
},
};
we use a third party module, fs-readfile-promise which basically turns node readFileSync into a promise.
But we struggle to mock implementation of this third party, to be able to produce two tests: one based on simulated read file (promise resolved) and one based on rejection.
Here is our test file:
const { readJsonFile } = require('../routes/json-file');
const readFilePromise = require('fs-readfile-promise');
jest.mock('fs-readfile-promise');
const resJson = jest.fn();
const resStatus = jest.fn();
const resSend = jest.fn();
const res = {
send: resSend,
status: resStatus,
json: resJson,
};
resJson.mockImplementation(() => res);
resStatus.mockImplementation(() => res);
resSend.mockImplementation(() => res);
describe('json routes', () => {
beforeEach(() => {
resStatus.mockClear();
resJson.mockClear();
resSend.mockClear();
});
describe('when there is an error reading file', () => {
beforeEach(() => {
readFilePromise.mockImplementation(() => Promise.reject('some error'));
});
it('should return given error', () => {
readJsonFile(null, res);
expect(readFilePromise).lastCalledWith('somefile.json', 'utf8'); // PASS
expect(resStatus).lastCalledWith(500); // FAIL : never called
expect(resSend).lastCalledWith({ "any": "value" }); // FAIL : never called
});
});
});
We tried to place readFilePromise.mockImplementation(() => Promise.reject('some error')); at the top, just after the jest.mock() without more success.
The third party code is basically something like:
module.exports = async function fsReadFilePromise(...args) {
return new Promise(....);
}
How can we mock and replace implementation of the module to return either a Promise.resolve() or Promise.reject() depending on our test setup to make our test case pass within res.send() or res.status() method?
The last 2 assertions never pass because the test doesn't wait for the promise in: const file = await readFilePromise(filePath, 'utf8'); to resolve or reject in this case, therefore res.send or res.status never get called.
To fix it, readJsonFile is async, you should await it in the test:
it('should return given error', async () => {
await readJsonFile(null, res);
...
})
How can we mock and replace implementation of the module to return
either a Promise.resolve() or Promise.reject() depending on our test
setup to make our test case pass within res.send() or res.status()
method
Exactly how you're doing it:
readFilePromise.mockImplementation(() => Promise.reject('some error'));
or
readFilePromise.mockImplementation(() => Promise.resolve('SomeFileContent!'));
I have a request call wrapped in a promise chain as follows
import * as request from 'request';
public async getData (): Promise<string> {
const token = await this.util.getToken();
const options = {
withCredentials: true,
Connection: 'keep-alive'
};
return new Promise<string>((resolve, reject) => {
request(this._Host, options, (err, data) => {
if (err != null) {
return reject('Fetch Failed');
}
const values = JSON.parse(data.body);
this._result = values.result;
return resolve('Fetch Success');
});
});
}
And I'm using jest to test this method using the following code
it('should fetch data', async(done) => {
const classobj = new ClassName(config);
(classobj as any).util.getToken.// tslint:disable-line
mockResolvedValue('some-token');
const spy = jest.mock('request', (path, options) => {
return Promise.resolve('Fetch Success');
});
const dispatcher = await classobj.getData().then(done());
});
However the code inside request block never gets triggered. But the test is displayed as running successfully as well. I also noticed that if I pass a values that's already resolved within the return new Promise then the code block is getting triggered without any issues. Is there something that's missing in this code to test the request call?
I'm writing tests on my RESTful API using Jest + supertest.
My test.js looked like this at first:
const crypto = require('crypto')
const request = require('supertest')
const app = require('./app')
const genUUID = () => {
return ([1e7]+1e3+4e3+8e3+1e11).replace(/[018]/g, c =>
(c ^ crypto.randomFillSync(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
)
}
let uuid1 = genUUID()
let uuid2 = genUUID()
describe('Test /uuids', () => {
it('Get list of uuids', () => {
request(app).get('/api/uuids/').then(res =>
expect(res.statusCode).toBe(200)
)
})
})
describe('Test /uuids/:uuid', () => {
it('Get info of a not-existed product', () => {
request(app).get('/api/uuids/' + uuid1).then(res =>
expect(res.statusCode).toBe(400)
)
})
})
It works and all tests are passed.
But I love the style of async/await, so I switched the promises to async/await.
... // The previous part remains unchanged
describe('Test /uuids', () => {
it('Get list of uuids', async() => {
const res = await request(app).get('/api/uuids/')
expect(res.statusCode).toBe(200)
})
})
describe('Test /uuids/:uuid', () => {
it('Get info of a not-existed product', async () => {
const res = await request(app).get('/api/uuids/' + uuid1)
expect(res.statusCode).toBe(400)
})
})
This time, errors are raised.
console.error api/uuids.js:247
ERR!error: bind message supplies 1 parameters, but prepared statement "Get lists of UUIDs" requires 6
....
● Test /uuids/:uuid › Get info of a not-existed product
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
Did I write the async/await correctly? Or was there any problems about async/await in Jest?
P.S. node version is v8.11.2
The promise version doesn't work actually. As said in comments (this and this), the test results are skipped, giving the false sense of passing the tests. Adding return to the promise results in the same errors as async/await case.
By mocking the app code with
const app = new (require('express'))()
app.get('/api/uuids', (req, res) => {
res.status(200).end()
})
app.get('/api/uuids/:uuid', (req, res) => {
res.status(200).end()
})
module.exports = app
The async/await one can tell there is sth wrong in GET /api/uuids/:uuid, while the promises one still report all tests are passed. So the async/await is correct instead.
And after some debug, it turns out that it's actually the problem of my code on querying database, so a postgres error was thrown in the first place.
Try to increase the timeout limit (by passing a number in miliseconds as second argument to it function):
describe('Test /uuids', () => {
it('Get list of uuids', async() => {
const res = await request(app).get('/api/uuids/')
expect(res.statusCode).toBe(200)
}, 30000)
})
describe('Test /uuids/:uuid', () => {
it('Get info of a not-existed product', async () => {
const res = await request(app).get('/api/uuids/' + uuid1)
expect(res.statusCode).toBe(400)
}, 30000)
})