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

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();
})
})
})

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

Async await test cases failed using jest javascript testing library

I am using Jest testing Library for some simple async/await functions. But it's failing again and again as I am very new to jest. and can you please answer what expect.assertions(1) do here
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: 1,
name: "test",
age: 20,
});
}, 1000);
});
}
test("test async await", async () => {
const data = await fetchData();
expect(data.id).toBe(1);
});
test("async await error", async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch("error");
}
});
As I pointed out in the comments, the test for the "failure" case doesn't really make sense if this fetchData is the real function because it never 'rejects'. To test the failure case in Jest, you'd need to somehow trigger the Promise.reject case.
If we assume this fetchData is a wrapper on an api call or something else, we could imagine something like this.
You might have a library or module that is making api calls like:
// api.js
const api = {
actualFetchData: () => {
// this is the function that actually connects
// to a data source and returns data
},
};
module.exports = api;
And your fetchData function which you're trying to test looks like:
// fetchData.js
const api = require("./api");
function fetchData() {
return api.actualFetchData();
}
module.exports = fetchData;
Then, assuming this structure matches what you're working on, you can mock the internals of fetchData and test both success and failure cases by mocking actualFetchData and using mockResolvedValue and mockRejectedValue.
// fetchData.test.js
const fetchData = require("./fetchData");
const api = require("./api");
jest.mock("./api");
const mockApiFetch = jest.fn();
api.actualFetchData = mockApiFetch;
describe("when the underlying fetch resolves", () => {
beforeEach(() => {
mockApiFetch.mockResolvedValue({
id: 1,
name: "test",
age: 20,
});
});
test("test async await", async () => {
const data = await fetchData();
expect(data.id).toBe(1);
});
});
describe("when the underlying fetch fails", () => {
beforeEach(() => {
mockApiFetch.mockRejectedValue(new Error("failed to get data"));
});
test("async await error", async () => {
expect(() => fetchData()).rejects.toThrow("failed to get data");
});
});
You'll notice I didn't use the expect.assertions because it didn't seem like it added anything to the test. Instead, just used toThrow with text that matches the error.
I realize this is making some assumptions about a system that you haven't fully described in the initial question so this may not be exactly what you're trying to get at. Hopefully it's close.

Jest testing mongoose model instantiation

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.

Sinon Spy not being called with right arguments

Background
I am trying to learn how to do a RESTful API following the TDD paradigm by reading a book on the subject (it is in brazillian):
https://leanpub.com/construindo-apis-testaveis-com-nodejs/read
The author encourages the use a sinon.js together with mocha.js.
I am getting close to the end, but I am failling to pass the test for my gnomeController.
Problem
The problem is that I am using sinon to assert that I am calling the gnomeController's get method using the given reponse object, which is in reallity a spy.
This spy is to make sure I call the reponse method with an "Error", but it appears I am calling the response with no arguments whatsoever, which is very confusing.
Code
gnomeController.js
module.exports = aGnomeModel => {
let Gnome = aGnomeModel;
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err));
}
return Object.freeze({
get
});
};
gnomeTest.js
const sinon = require("sinon");
const gnomesControllerFactory = require("gnomesController.js");
const Gnome = require("gnomeModel.js");
describe("Controllers: Gnomes", () => {
describe("get() gnomes", () => {
it("should return 400 when an error occurs", () => {
const request = {};
const response = {
send: sinon.spy(),
status: sinon.stub()
};
response.status.withArgs(400).returns(response);
Gnome.find = sinon.stub();
Gnome.find.withArgs({}).rejects("Error");
const gnomesController = gnomesControllerFactory(Gnome);
return gnomesController.get(request, response)
.then(arg => {
console.log(arg);
sinon.assert.calledWith(response.send, "Error");
});
});
});
});
Question
I am using the latest versions of both libraries.
What is wrong in my code, why is the reponse being called with no arguments?
Solution
After much debugging, I found out that the solution is to replace:
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err));
}
with:
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err.name));
}
Which is not explained in the book. Kinda wish I could give more feedback on it, but so far it is what it is.

How to unit test/stub async call in redux

How is the proper way to unit test the following redux async action?
const client = contentful.createClient(clientConfig);
export const fetchNavigation = () => {
return dispatch => {
return client.getEntries({content_type: 'navigation'})
.then((entries) => {
console.log('All entries for content_type = navigation')
dispatch(receiveNavigation(entries))
})
.catch(error => {
console.log('Something went wrong');
dispatch(fetchNavigationFailure(error));
});
}
}
I don't know how to customise the web request response body performed by client.getEntries. I think that replacing the getEntries function with my own one would do the trick. However, I don't know where to start to do that.
Here is the unit test I wrote:
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('fetchNavigation', () => {
it('creates RECEIVE_NAVIGATION when fetching navigation is done', () => {
// Here I should prepare the client.getEntries() returned promise
const expectedBodyResponse = { includes: ['do something', 'yay!'] }
const expectedActions = [
{ type: actions.RECEIVE_NAVIGATION, navigation: expectedBodyResponse }
]
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchNavigation())
.then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
IMO mocking getEntries (and probably createClient) seems to be the right way to do. :)
It depends how you load the contentful sdk. As I see you're using ES Modules and Jasmine, right?
To mock the getEntries function you have to mock the createClient as the client is not accessible from within your test.
I think this this answer might be what you're looking for.
I just wrote down an example.
import contentful from 'contentful';
export const fetchNavigation = () => {
return (dispatch) => {
return contentful.createClient({ accessToken: 'fooo', space: 'bar' })
.getEntries({ content_type: 'navigation' })
.then(() => {
dispatch('yeah');
})
.catch(error => console.error('Something went wrong', error));
};
};
import { fetchNavigation } from '../Action';
import * as contentful from 'contentful';
describe('Contentful mocking', () => {
it('should be possible to mock Contentful', (done) => {
const client = { getEntries: () => { return Promise.resolve(); } };
const spy = {
fn: (value) => {
expect(value).toBe('yeah');
done();
},
};
spyOn(contentful.default, 'createClient').and.returnValue(client);
fetchNavigation()(spy.fn);
});
});
I had to move the createClient call into the action itself, because otherwise I don't think it's possible to reach and mock it when it's hidden in the module scope. I then used the import * as contentful from 'contentful' to mock and overwrite the needed functionality and to have the flexibility to adjust everything to my needs.
The usage of the createClient feels a bit unfortunate for me. I'd probably restructure everything a bit and would pass the client as dependency of all the actions? This way the mocking would become way easier and when you also have several action modules, there is most probably no need to initialize the client several times?
I solved in this way.
First I moved the creation of the client to its own file with functions initClient() and getClient(). The module is called contentfulClient.
Then, I found out that it is possible to mock the function of an instantiated object in sinon:
import * as contentful from './services/contentfulClient';
const client = contentful.initClient(clientConfig);
const navigation = {
items: ['page1', 'page2']
};
// Returns a promise with navigation as content
sinon.stub(client, 'getEntries').resolves(navigation);
// Assert
return store.dispatch(actions.fetchNavigation())
.then( () => { expect(store.getActions()).toEqual(expectedActions)
})

Categories

Resources