Testing async redux with third party API calls - node.js

I'm new to redux and programming in general and am having trouble wrapping my head around certain unit testing concepts.
I have some async actions in redux, which involve calls to a third party API (from the 'amazon-cognito-identity-js' node module).
I have wrapped the external API call in a promise function, and I call this function from the 'actual' action creator. So for testing I just want to stub the result of externalAWS() function so that I can check that the correct actions are being dispatched.
I'm using redux-thunk for my middleware.
import { AuthenticationDetails,
CognitoUser
} from 'amazon-cognito-identity-js';
export function externalAWS(credentials) {
//This is required for the package
let authenticationDetails = new AuthenticationDetails(credentials);
let cognitoUser = new CognitoUser({
//Construct the object accordingly
})
return new Promise ((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: result => {
resolve(result);
},
onFailure: error => {
reject(error)
}
})
}
}
export function loginUser(credentials) {
//return function since it's async
return dispatch => {
//Kick off async process
dispatch(requestLogin());
externalAWS(credentials)
.then((result) => {
dispatch(receiveLogin(result.getAccessToken().getJwtToken(), credentials.username))
})
.catch((error) => {
dispatch(failedLogin(error.message, etc))
})
}
}
I don't have any test code yet because I am really not sure how to approach this. All the examples deal with mocking a HTTP request, which I know is
what this boils down to, so am I supposed to inspect the HTTP requests in my browser and mock them out directly?
It's further complicated by the fact that the second argument of authenticateUser is not even a plain callback, but an object with callbacks as it's values.
Can anyone offer some advice on whether my intention in unit testing the async function is correct, and how I should approach it? Thank you.
Edit: I'm testing in Jest.
Edit2: Request Headers
First POST request,
Second POST request
Edit3: Split the function, trying my best to isolate the external API and create something that is 'easily mock/stub-able'. But still running into issues of how to properly stub this function.

Redux thunk gives you the ability to dispatch future actions within the context of a main action that kicks off the process. This main action is your thunk action creator.
Therefore tests should focus on what actions are dispatched within your thunk action creator according to the outcome of the api request.
Tests should also look at what arguments are passed to your action creators so that your reducers can be informed about the outcome of the request and update the store accordingly.
To get started with testing your thunk action creator you want to test that the three actions are dispatched appropriately depending on whether login is successful or not.
requestLogin
receiveLogin
failedLogin
Here are some tests I wrote for you to get started using Nock to intercept http requests.
Tests
import nock from 'nock';
const API_URL = 'https://cognito-idp.us-west-2.amazonaws.com/'
const fakeCredentials = {
username: 'fakeUser'
token: '1234'
}
it('dispatches REQUEST_LOGIN and RECEIVE_LOGIN with credentials if the fetch response was successful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(200, Promise.resolve({"token":"1234", "userName":"fakeUser"}) })
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'RECEIVE_LOGIN', token: '1234', userName: 'fakeUser'});
})
});
it('dispatches REQUEST_LOGIN and FAILED_LOGIN with err and username if the fetch response was unsuccessful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(404, Promise.resolve({"error":"404", "userName":"fakeUser"}))
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'FAILED_LOGIN', err: '404', userName: 'fakeUser'});
})
});

So I figured it out in the end.
First, I had to require() the module into my test file (as opposed to ES6 import). Then I removed the promise for now since it was adding a layer of complexity and combined everything into one function, let's call it loginUser(). It is a redux async action, that dispatches one action upon being called, and then a success or failure action depending on the result of the API call. See above for what the API call looks like.
Then I wrote the test as follows:
const CognitoSDK = require('/amazon-cognito-identity-js')
const CognitoUser = CognitoSDK.CognitoUser
//Set up the rest of the test
describe('async actions', (() => {
it('should dispatch ACTION_1 and ACTION_2 on success', (() => {
let CognitoUser.authenticateUser = jest.fn((arg, callback) => {
callback.onSuccess(mockResult)
})
store.dispatch(loginUser(mockData))
expect(store.getActions()).toEqual([{ACTION_1}, {ACTION_2}])
}))
}))
So basically once requiring the module in, I mocked it in Jest and did a mock implementation too, so that I could access the onSuccess function of the callback object.

Related

How to correctly create promise that resolves when webhook is called

I just watched James Snell's excellent talk on Promises in Node: https://www.youtube.com/watch?v=XV-u_Ow47s0
This got me thinking about a piece of code I have that calls a web service, supplying a webhook URL, which I'm sure is suboptimal. But I don't know how I should write this correctly. To paraphrase my code:
const activeRequests = new Map();
function makeRequest(id:string) {
return new Promise(resolve => {
fetch(`http://external.com/someComplexCalculation`,
{
method:"POST",
body: { webhook:`http://myservice.com/webhook/${id}` }
})
.then(res => {
activeRequests.set(id, resolve);
});
});
}
expressApp.post('/webhook/:id', (req, res) => {
const active = activeRequests.get(req.params.id);
if (active) {
active();
}
res.sendStatus(200);
});
I've skipped all the error handling for clarity, but you get the idea. Is this the best way to do this? The end goal is for the caller of makeRequest to receive a promise that will resolve when the webhook is called.
The expected flow is that my application calls await makeRequest(xx) which calls some external service - that service then calls back to my application via the /webhook/xx URL when it is done with its processing, at which point the original makeRequest promise resolves.
I currently store the resolve function from the new Promise in a Map since I can't call that resolve function until the webhook is invoked, but that is the crux of my question: is there a better way to achieve this that doesn't involve this odd arrangement?

Axios POST request as a promise

I am writing an async function made up of quite a few different different methods and the order of execution is important. I want to make an axios post request early on in the function therefore I am trying to turn the method I used in to a promise to be sure that it executes sequentially in the main function. I am including both the way I did it initially which was working and the method I made to abstract some of the code which is not working I believe due to the fact that it is an arrow function with an await command inside of it. My question is one: will my initial method of calling the post request execute sequentially and if not how can I fix this abstracted method so that it will work?
working attempt (code inside of main function):
var form = new FormData();
form.append("arguments", JSON.stringify({"ID":`${zohoLeadID}`, "folderNumber": `https://drive.google.com/drive/folders/${rootFolder}`}));
const axiosRes = await axios.post(
'https://myreturnaddress.net',
form,
{ headers: form.getHeaders() }
);
failed attempt at promise:
async function postData(zohoLeadID, root) {
let form = new FormData();
form.append("arguments", JSON.stringify({"ID":`${zohoLeadID}`, "folderNumber": `https://drive.google.com/drive/folders/${root}`}));
return new Promise((resolve, reject) => {
try {
const axiosRes = await axios.post('myhttpsaddress.net', form, {headers: form.getHeaders()})
resolve(axiosRes);
} catch(err) {
reject(err);
}
})
}

How to test a controller in Node.js using Chai, Sinon, Mocha

I've been learning how to write better unit tests. I am working on a project where controllers follow the style of 'MyController' shown below. Basically an 'async' function that 'awaits' on many external calls and returns a status with the results. I have written a very basic test for an inner function 'dbResults'. However, I am unsure of how to go about testing if the entire controller function itself returns a certain value. In the example below. I was wondering if anyone can help me figure out what is the proper way to test the final result of a function such as 'getUserWithId'. The code written below is very close but not exactly what I have implemented. Any suggestions would be appreciated.
Controller
export const MyController = {
async getUserWithId(req, res) {
let dbResults = await db.getOneUser(req.query.id);
return res.status(200).json({ status: 'OK', data: dbResults });
}
}
Current Test
describe ('MyController Test', () => {
describe ('getUserWithId should return 200', () => {
before(() => {
// create DB stub
});
after(() => {
// restore DB stub
});
it('should return status 200', async () => {
req = // req stub
res = // res stub
const result = await MyController.getUserWithId(req, res);
expect(res.status).to.equal(200);
});
});
});
I would suggest using an integration-test for testing your controller rather than a unit-test.
integration-test threats the app as a black box where the network is the input (HTTP request) and 3rd party services as dependency that should be mocked (DB).
Use unit-test for services, factories, and utils.
Use integration-test for external interfaces like HTTP and WebSockets
You can add e2e-test as well but if you have only one component in your setup you integration-test will suffice.

How to stub a service with Sinon in an integration test?

I'm trying to do some integration tests for my api in express.
My API's structure is something like:
app -> routes -> controllers -> services
Because I already have unit tests, my idea is only test that all that components are connected in the correct way.
So my idea was created an stub with Sinon for the service, and only check the responses of the controller with supertest.
When I run a single test everything is ok. The problem is when I run more than one unit test for different controllers, the stub doesn't work in the second run.
I think it's because the app is already saved in cache as a module, so sinon can't stub the service.
Some examples of my code:
controller.js
const httpStatus = require('http-status');
const { service } = require('../services/croupier');
/**
* Execute lambda tasks for candidates
* #public
*/
exports.task = async (req, res, next) => {
try {
const result = await service({
body: req.body,
authorizer: req.authorizer
});
console.log('res', result);
res.status(httpStatus.OK).json(result);
} catch (error) {
next(error);
}
};
foo.integration.test.js
const request = require('supertest');
const httpStatus = require('http-status');
const sinon = require('sinon');
const mongoose = require('../../../database');
const deleteModule = module => delete require.cache[require.resolve(module)];
const requireUncached = module => {
deleteModule(module);
return require(module);
};
describe('Foo - Integration Test', async () => {
describe('POST /v1/foo', () => {
const fooService = require('../../services/foo');
const stub = sinon.stub(fooService, 'service');
let db;
before(async () => {
db = await mongoose.connect();
});
afterEach(async () => {
sinon.restore();
});
after(async () => {
await db.close();
});
it('the api should response successfully', async () => {
stub.returns({});
const payload = { task: 'task', payload: [{ pathParameters: {}, body: {} }] };
const app = requireUncached('../../../app');
await request(app)
.post('/api/foo')
.send(payload)
.expect(httpStatus.OK);
});
it('the api should response with an error', async () => {
stub.throwsException();
const payload = { task: 'task', payload: [{ pathParameters: {}, body: {} }] };
const app = requireUncached('../../../app');
await request(app)
.post('/api/foo')
.send(payload)
.expect(httpStatus.INTERNAL_SERVER_ERROR);
});
});
});
The other integration tests have the same structure. I've also tried using proxyquire but didn't work.
Also I tried deleting cache of de app.js with any success.
Any ideas?
Context: integration test.
I agree with your idea: "test that all that components are connected in the correct way". Then what you need is spy, not stub. When there is a case / condition, you need to setup preconfigured/dummy data (up mongodb with specific data), turn on HTTP server, call HTTP request with specific data (post / get with specific query), and check the HTTP response for correct status, etc. The spy needed to check/validate/verify whether your service get called with correct parameter and response with correct result. This test validate you have correctly configured route - controller to a service for specific HTTP request.
You must have question: How to test negative scenario? For example: 404, 500. Then you need to know which specific scenario do what, which result negative condition. For example: if request come with unknown ID query, then response will be 404. Or if express not connected to database, then response will be 500. You need to know the real scenario, and again provide the require setup to produce the negative response.
For problem: "When I run a single test everything is ok. The problem is when I run more than one unit test for different controllers, the stub doesn't work in the second run.". There are several possible solutions, the main point is: you must make sure that the conditions for specific scenario/case are correctly prepared.
You can do:
create sandbox, to make sure no other stub service run between test cases.
start up fresh http (and or db) server before and shut down the server after the test run for each services, (for example: start the app and use real http client - as alternative to supertest)
run on debug mode to find out why the second stub not run or not get called or not work,
change implementation from stub to spy, you have already had a unit test, you just need to check whether the service get called or not, and then check the overall response.
Hope this helps.

How write simple Jest mock for express-session's req.session.destroy()

I am writing a grapqhl server that has a simple logout mutation. Everything works as expected when I run the server and I can log out by destroying the session and clearing the cookie just fine.
Here is the resolver:
export default async (root, args, context) => {
console.log("THIS WILL LOG")
await new Promise((res, rej) =>
context.req.session.destroy(err => {
if (err) {
return rej(false);
}
context.res.clearCookie("qid");
return res(true);
})
);
console.log("NEVER HERE BEFORE TIMEOUT");
// 4. Return the message
return {
code: "OK",
message: "You have been logged out.",
success: true,
item: null
};
};
I am attempting to write a simple test just to verify that the req.session.destroy and res.clearCookie functions are actually called. At this point I AM NOT attempting to test if a cookie is actually cleared, as I am not actually starting up the server, I am just testing that the graphql resolver was ran correctly and that it called the right functions.
Here is a portion of my test:
describe("confirmLoginResolver", () => {
test("throws error if logged in", async () => {
const user = await createTestUser();
const context = makeTestContext(user.id);
context.req.session.destroy = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
context.res.clearCookie = jest.fn();
// this function is just a helper to process my graphql request.
// it does not actually start up the express server
const res = await graphqlTestCall(
LOGOUT_MUTATION, // the graphql mutation stored in a var
null, // no variables needed for mutation
null // a way for me to pass in a userID to mock auth state,
context // Context override, will use above context
);
console.log(res);
expect(context.req.session.destroy).toHaveBeenCalled();
// expect(res.errors.length).toBe(1);
// expect(res.errors).toMatchSnapshot();
});
});
Again, everything works correctly when actually running the server. The problem is that when I attempt to run the above test, I always get a jest timeout:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
The reason is that the await section of above resolver will hang because it's promise.resolve() is never being executed. So my console will show "THIS WILL LOG", but will never get to "NEVER HERE BEFORE TIMEOUT".
I suspect I need to write a better jest mock to more accurately simulate the callback inside of context.req.session.destroy, but I can not figure it out.
Any ideas how I can write a better mock implementation here?
context.req.session.destroy = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
Is not cutting it. Thoughts?
Try
context.req.session.destroy = jest
.fn()
.mockImplementation((fn) => fn(false));

Resources