Jest testing mongoose model instantiation - javascript

I'm trying to test a REST API built with express and mongoose, I'm using jest and supertest for the http calls; also I'm relatively new to testing with javascript.
When testing a creation url I wan't to make sure the instantiation is called using just the req.body object but I'm not sure how to do it, after reading a lot about differences between mock objects and stubs and some of the Jest documentation my last try looks like this:
test('Should instantiate the model using req.body', done => {
const postMock = jest.fn();
const testPost = {
name: 'Test post',
content: 'Hello'
};
postMock.bind(Post); // <- Post is my model
// I mock the save function so it doesn't use the db at all
Post.prototype.save = jest.fn(cb => cb(null, testPost));
// Supertest call
request(app).post('/posts/')
.send(testPost)
.then(() => {
expect(postMock.mock.calls[0][0]).toEqual(testPost);
done();
})
.catch(err => {throw err});
});
Also I would like to know how to manually fail the test on the promise rejection, so it doesn't throws the Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

As it stands, you're performing more of a integration test rather than isolating the route handler function itself and testing just that.
First I would break away the handler for /posts/ to its own file (assuming you haven't done this already):
controllers/post-controller.js
const Post = require('./path/to/models/post')
exports.store = async (req, res) => {
const post = await new Post(req.body).save()
res.json({ data: post }
}
Next simply use the handler wherever you defined your routes:
const express = require('express')
const app = express()
const postController = require('./path/to/controllers/post-controller')
app.post('/posts', postController.store)
With this abstraction we can now isolate our postController.store and test that it works with req.body. Now since we need to mock mongoose to avoid hitting an actual database, you can create a mocked Post like so (using the code you already have):
path/to/models/__mocks__/post.js
const post = require('../post')
const mockedPost = jest.fn()
mockedPost.bind(Post)
const testPost = {
name: 'Test post',
content: 'Hello'
}
Post.prototype.save = jest.fn(cb => {
if (typeof cb === 'function') {
if (process.env.FORCE_FAIL === 'true') {
process.nextTick(cb(new Error(), null))
} else {
process.nextTick(cb(null, testPost))
}
} else {
return new Promise((resolve, reject) => {
if (process.env.FORCE_FAIL === 'true') {
reject(new Error())
} else {
resolve(testPost)
}
})
}
})
module.exports = mockedPost
Notice the check for process.env.FORCE_FAIL if for whatever reason you wanted to fail it.
Now we're ready to test that using the req.body works:
post-controller.test.js
// Loads anything contained in `models/__mocks__` folder
jest.mock('../location/to/models')
const postController = require('../location/to/controllers/post-controller')
describe('controllers.Post', () => {
/**
* Mocked Express Request object.
*/
let req
/**
* Mocked Express Response object.
*/
let res
beforeEach(() => {
req = {
body: {}
}
res = {
data: null,
json(payload) {
this.data = JSON.stringify(payload)
}
}
})
describe('.store()', () => {
test('should create a new post', async () => {
req.body = { ... }
await postController(req, res)
expect(res.data).toBeDefined()
...
})
test('fails creating a post', () => {
process.env.FORCE_FAIL = true
req.body = { ... }
try {
await postController.store(req, res)
} catch (error) {
expect(res.data).not.toBeDefined()
...
}
})
})
})
This code is untested, but I hope it helps in your testing.

Related

How to Inject multiple mock promises to unit test the fetch in JS?

I'm trying to setup unit tests for the api calls in my JS project.
Able to set it up for a single API call, using the following format
describe('Token refresh success', () => {
beforeAll(() => {
global.fetch = () =>
Promise.resolve({
json: () => Promise.resolve(mockTokenCreateSuccess),
})
})
afterAll(() => {
global.fetch = unmockedFetch
})
test('testTokenRefreshSuccess', async () => {
const tokenData = await c.tokenRefresh();
expect(tokenData.access_token).toEqual('SYoHdm4yw');
expect(tokenData.refresh_token).toEqual('QxJ3yEgX4NThbTE66u7lshWTpQkRBilq');
});
})
Now this format works great, and I can create individual tests by injecting one promise.
Now, there is a case I want to unit test where a particular API call is made twice. I need to inject fail response the first time, and success response the second time.
I tried the following approach, but did not work:
describe('Token refresh trigger as expected on create token fail', () => {
beforeAll(() => {
global.fetch = () => [
Promise.resolve({
json: () => Promise.reject(mockError(400)),
}),
Promise.resolve({
json: () => Promise.resolve(mockTokenCreateSuccess),
})
]
})
afterAll(() => {
global.fetch = unmockedFetch
})
test('testTokenRefreshTriggerOnTokenCreateFail', async () => {
const tokenData = await c.tokenRefresh();
expect(tokenData.access_token).toEqual('jkhjk');
expect(tokenData.refresh_token).toEqual('dfdfdf');
});
})
and my tokenRefresh() function is supposed to get called 2 times if it gets 400 error.
async tokenRefresh(retryCount = 0) {
console.log('tokenRefresh');
const request = this.getTokenRefreshRequest();
try {
const response = await this.fetchData(request);
console.log('token refresh success', response);
return { access_token, refresh_token };
} catch (err) {
if ((err.status == 400 || err.status == 401) && retryCount < 2) {
await this.tokenRefresh(retryCount++);
} else {
throw new Error(`unable to refresh a token from API ${err.status}`);
}
}
};
I'm able to verify that by using single promise
Promise.resolve({
json: () => Promise.reject(mockError(400)),
})
the tokenRefresh() gets called again as expected, but in the second time I could not figure out how to pass the success promise, at the moment it fails the second time too.
I was able to find a working combination
beforeAll(() => {
global.fetch = fetchMock.mockRejectOnce(customError) // makes sure first api call fails
.mockResponseOnce(JSON.stringify(successResponse)) // makes sure second api call succeeds
})
This made my test pass

How to mock s3 putObject with promise function with jest

I see the answer here Mocking aws-sdk S3#putObject instance method using jest, but it's not working as-is and it's not explained at all so I don't know what's going on.
I have a function:
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
exports.saveImageToS3 = async (params) => {
try {
const s3resp = await s3.putObject(params).promise();
/* Happy path response looks like this:
data = {
ETag: "\"6805f2cfc46c0f04559748bb039d69ae\"",
VersionId: "psM2sYY4.o1501dSx8wMvnkOzSBB.V4a" // version optional
}*/
if (s3resp.hasOwnProperty('ETag')) { // expected successful response
return { success: true, key: params.Key } // returning key of item if we have reason to think this was successful.
} else {
console.warn("Unexpected s3 response format: ", s3resp)
return s3resp;
}
} catch (err) {
throw err;
}
}
I'd like to test if it works.
I do not understand how to mock the s3.putObject function.
I have tried:
describe('FUNCTION: saveImageToS3', () => {
const mockedPutObject = jest.fn();
jest.mock('aws-sdk', () => {
return class S3 {
putObject(params, cb) {
console.log("Mocked putObject function");
mockedPutObject(params, cb);
}
}
});
test('returns success object with correct key value', async () => {
await expect(await utils.saveImageToS3(params)).toEqual({ success: true, key: `original/testCamId/testCamId___2022-04-06T06-30-59Z.jpg` })
})
})
per the above-linked answer, but the test fails (times out, actually) and the output "Mocked putObject function" never is written to the console, telling me the mocked aws-sdk isn't being used...

Unit Test: Stub/rewire a function inside a server request

I want to test a route that makes external api calls.
I would like to stub the functionThatShouldBeStubbed so I can skip the external api call and focus on testing the route instead.
I am using Sinon and rewire, because if I understood correctly I cannot stub a function that was exported the way it currently is.
However, it seems like even though rewire replaced the function, my test is still making external api call. It seems like sinon is not aware that the function was rewired. How can I make this situation work?
//--------------------------
//../target.js
const functionThatShouldBeStubbed = async () => {
const results = await external_API_call();
return results;
}
module.exports = {
functionThatShouldBeStubbed,
/*more other functions*/
}
//--------------------------
//../index.js
app.use(require('endpoint.js'));
//--------------------------
//endpoint.js
const { functionThatShouldBeStubbed } = require("target.js");
router.post('endpoint', async(req, res) => {
//do lots of stuff
const results = await functionThatShouldBeStubbed();
if(results.error) { return res.status(207).send({ /*stuff */})}
//...more stuff
})
//--------------------------
//test.js
const server = require("../index.js");
const rewire = require('rewire')
const restoreTarget = rewire('../target.js');
describe("Should return appropriate error code to requester", function () {
it("Should return 207 in this case", function (done) {
const targetStub = sinon.stub().resolves({msg: 'fake results', statusCode: 207})
const targetRewired = restoreTarget.__set__("functionThatShouldBeStubbed", targetStub);
chai.request(server)
.post("/endpoint")
.send('stuff over')
.catch((error) => {
console.log("Error: ", error)
done();
})
.then((res) => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
restoreTarget();
targetStub.restore();
done();
})
})
})
Many thanks!
Edit: updated code for more detail
Edit2: updated code again to show import method
You shouldn't need rewire at all here based on how your module is being exported. The following should work
//test.js
const target = require ("../target");
const server = require("../index");
describe("Should return appropriate error code to requester", () => {
it("Should return 207 in this case", done => {
const targetStub = sinon
.stub(target, "functionThatShouldBeStubbed")
.resolves({msg: 'fake results', statusCode: 207})
chai.request(server)
.post("/endpoint")
.send('stuff over')
.then(res => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
targetStub.restore();
done();
})
})
})

jest everything after expect isn't called

my jest is not working as I expect it. See:
const res = {
send: (content) => {
expect(content).toEqual({
app_status: 501,
errors: {
jwt: {
location: 'body',
param: 'jwt',
value: undefined,
msg: 'The jwt is required'
}
}
});
console.log("after expect");
done();
},
};
Basically EVERYTHING after the expect(content).toEqual ... in res.send is not called. I find that very confusing. I am getting no error except for that my test's are taking too long (because done) is not called and the test is not "closed". So my question is, am I missing something obviously?
The following should work fine. I added asynchronous since send may be called asynchronously:
const createResponse = () => {
var resolve;
const p = new Promise(
(r,reject)=>resolve=r
);
return [
{
send: (value) => {
resolve(value)
}
},
p
];
};
test('(async) fail', done => {
//Router
const router = express.Router();
//Endpoint to fetch version
router.get('/api/version', (req, res) => {
setTimeout(x=>res.send('v1'),10)
});
const request = {
method: 'GET'
};
let [response,p] = createResponse()
router.stack[0].handle(request, response, () => {});
p.then(
x=>expect(x).toBe('v2'),
reject=>expect("should not reject").toBe(reject)
).then(
x=>done()
//,x=>done() //this will cause all tests to pass
);
});
To answer your question in the comment; consider the following code:
const express = require('express');
const router = express.Router();
//Endpoint to fetch version
router.get('/api/version', (req, res) => {
res.send("Hello World");
});
const request = {
method: 'GET'
};
const response = {
send: (value) => {
debugger;//pause here and see the stack
throw new Error("Hello Error.");
}
};
router.stack[0].handle(
request,
response,
//this is the function done in route.js:
// https://github.com/expressjs/express/blob/master/lib/router/route.js#L127
err => {
console.error(err.message);//console errors Hello Error.
}
);
Send throws an error but send is called by your mock which is called by express here. So express catches the exception and then ends up here (done is not from just but your callback).
So it'll call your callback with an error, skipping done from jist and not throwing anything (maybe showing something in log). Since your callback doesn't do anything it times out.
You could try to call done from jist in the callback (at console.error(err.message);).
[UPDATE]
Careful trying to catch the error thrown by expect the following will tell me 1 test passed:
test('(async) fail', done => {
try{
expect(true).toBe(false);
}catch(e){
}
done();
});

Mocha Unit Testing of Controller resolving promise coming from services

I have controller :
function(req, res) {
// Use the Domain model to find all domain
CIO.find(function(err, CIOs) {
if (err) {
response = responseFormat.create(false, "Error getting CIOs", err, {});
res.status(400).json(response);
} else {
var metrics = {
"count": CIOs.length
};
// .then means it will wait for it to finish, then let you have the result
var promises = [];
for (i in CIOs) {
promises.push(Analysis.structureMetrics(CIOs[i].toObject()))
}
var output = []
var errors = []
Q.allSettled(promises)
.then(function(results) {
for (i in results) {
if (results[i].state === "rejected") {
console.log(results[i])
errors.push(results[i].reason.errors)
output.push(results[i].reason)
} else {
output.push(results[i].value)
}
}
}).then(function() {
response = responseFormat.create(true, "List of all CIOs", output, metrics, errors);
res.status(200).json(response);
})
}
});
};
and cio.test file :
describe('/cio', function() {
describe('GET', function() {
//this.timeout(30000);
before(function() {
});
it('should return response', function(done) {
var response = http_mocks.createResponse({eventEmitter: require('events').EventEmitter})
var request = http_mocks.createRequest({
method: 'GET',
url: '/cio',
})
//var data = JSON.parse( response._getData() );
response.on('end', function() {
response.statusCode.should.be.equal(400);
done();
})
cioCtrl.getCIOs(request, response);
});
});
});
getting Error
Error: timeout of 10000ms exceeded. Ensure the done() callback is being called in this test
1>I have already tried increasing the time, but It doesn't work.
2> What I found is response.('end', function(){}) is not getting called, but not sure why
Any help would be appreciated.
Thanks!
Very good approach for unit testing is using the dependency injection.
For this, your controller file should be something like this:
module.exports = class MyController {
constructor(CIO) {
this._CIO = CIO;
this.handler = this.handler.bind(this);
}
handler(req, res) {
// your code here using this._CIO
}
};
Than in your main file, you create instance of controller:
const MyController = require('./controllers/my-controller');
// require your CIO, or create instance...
const CIO = require('./CIO');
const myController = new MyController(CIO);
You simply then pass instance of controller, or it's handler function to the place where it will be used.
Using this approach allows you to test well.
Assume your 'it' will look something like this:
it('should work', function(done) {
const fakeCIO = {
find: function() {
done();
}
};
const myController = new MyController(fakeCIO);
myController.handler();
});
Basic differences between testing techniques:
unit test - you test one single unit, how it calls functions, makes assignments, returns values
integration test - you add database to your previous test and check how it is stored/deleted/updated
end-to-end test - you add API endpoint to previous integration test and check how whole your flow works
Update:
Using async/await approach you will be able to test more things using your controller. Assume modifying it in something like this:
async function(req, res) {
try {
const CIOs = await CIO.find();
const metrics = {
"count": CIOs.length
};
const promises = CIOs.map(el => Analysis.structureMetrics(el.toObject());
for(const promise of promises) {
const result = await promise();
// do whatever you need with results
}
} catch(err) {
const response = responseFormat.create(false, "Error getting CIOs", err, {});
res.status(400).json(response);
}
Using such approach, during unit testing you can also test that your controller calls methods:
responseFormat.create
Analysis.structureMetrics
res.status
res.json
test catch branch to be executed
All this is done with fake objects.
And sure using OOP is not mandatory, it's just a matter of habits, you can do the same using functional style, with closures, for example.

Categories

Resources