I'm attempting to write a test that checks the behaviour of my app depending on the response received from an API. To do this, I'm trying to make the response to the request I make have the config that I need.
An example is checking to see that my app redirects to the home page after logging in. I need the response to be HTTP 200 and have any value for an API key.
I am using axios to make the requests
Currently I have tried the following libraries with no success
moxios
axios-mock-adapter
nock
Does anyone have any experience with mocking remote HTTP requests and their responses?
EDIT: If it helps, I am using Mocha and Chai for tests
it('Does stuff', function () {
moxios.stubRequest('/auth/get_api_key', {
status: 200,
responseText: 'hello'
})
return this.app.client.setValue('[name="username"]', 'testing').then(() => {
return this.app.client.setValue('[name="password"]', 'testing').then(() => {
return this.app.client.submitForm('#login-form').then(() => {
return this.app.client.getRenderProcessLogs().then(function (logs) {
console.log(logs)
})
})
})
})
})
The code above is what I'm using to see if the request goes through and it outputs this
[ { level: 'SEVERE',
message: 'http://127.0.0.1:8000/auth/get_api_key/ - Failed to load resource: net::ERR_CONNECTION_REFUSED',
source: 'network',
timestamp: 1510082077495 } ]
My example was mostly taken from the moxios docs.
First in your test import the libraries you need. Afterwards, you'll handle creating and destroying the mock axios server in your beforeEach and afterEach hooks. You'll then pass a sinon.spy() into axios' get request you want to spy on.
This get request will be mocked by moxios and you can access it through moxios.wait(() => { moxios.request.mostRecent(); }); Inside the wait() method you can set the response you want on the most recent requests.
Afterwards, just like when you use axios you'll use the then() function to get your response. Inside the then() you can create your tests for that specific response. When you're done with your specs call done(); to close the request and move the specs along.
import axios from 'axios';
import moxios from 'moxios';
import sinon from 'sinon';
import { equal } from 'assert'; //use the testing framework of your choice
beforeEach(() => {
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
it('Does stuff', (done) => {
moxios.withMock(() => {
let onFulfilled = sinon.spy();
axios.get('/auth/get_api_key').then(onFulfilled);
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: {
apiKey: 1234567890
}
}).then((res) => {
equal(onFulfilled.called, true);
equal(res.status, 200);
equal(res.response.apiKey, 1234567890);
done();
})
})
})
});
Related
I'm intercepting my login and logout routes in my functional tests with Cypress. (I have to stub them because the Magic technology I'm using for authentication does NOT support a test mode for the server side SDK, yet.)
Here is the code for the routes:
import {
loginRoute,
logoutRoute,
} from 'features/user-authentication/user-authentication-api';
// ...
cy.intercept(loginRoute, request => {
request.reply({
headers: {
'Set-Cookie': `magic-auth-token=${Cypress.env(
'validMagicAuthToken',
)}`,
},
statusCode: 200,
body: { success: true },
});
});
cy.intercept(logoutRoute, request => {
request.reply({
headers: {
'Set-Cookie': `magic-auth-token=; Max-Age=-1; Path=/`,
},
statusCode: 302,
});
});
I'm mimicking the original route's behavior, where they add and remove cookies. The login route's stub works perfectly. However, the stub for the login route does not.
The original logout route looks like this:
import { parse, serialize } from 'cookie';
// ...
function removeTokenCookie<T>(response: NextApiResponse<T>) {
const cookie = serialize(TOKEN_NAME, '', {
maxAge: -1,
path: '/',
});
response.setHeader('Set-Cookie', cookie);
}
const logoutHandler: NextApiHandler = async (request, response) => {
const session = await getSession(request);
if (session) {
await magic.users.logoutByIssuer(session.issuer);
}
removeTokenCookie(response);
response.writeHead(302, { Location: '/' });
response.end();
};
How can I remove the cookies using the logout route's stub? For some reason the cookie does NOT get removed when I set the headers as I did above.
Cypress has the clearCookie command, but it can't be used inside the intercept callback.
cy.intercept(logoutRoute, request => {
cy.clearCookie('magic-auth-token')
request.reply...
})
This is the error
CypressError
Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.
The cy command you invoked inside the promise was: cy.clearCookie()
Looking at the source code for clearCookie, it boils down to the internal command
Cypress.automation('clear:cookie', { name: <cookie-name> })
While it's an internal command, it's use has been demo'd here Cypress Automation and here Testing an Application in Offline Network Mode
The type definitions were added recently Add type for Cypress.automation #7573
Here's a proof of concept,
it('clears cookies in intercept', () => {
cy.setCookie('magic-auth-token', '1234')
cy.getCookies().should('have.length', 1)
cy.intercept('*', (req) => {
Cypress.automation('clear:cookie', { name: 'magic-auth-token' })
})
cy.visit('http://example.com').then(() => {
// after the request has been intercepted
cy.getCookies().should('have.length', 0)
})
})
I am using Promise and axios in react to call POST api and fetch list of records.
Issue is when multiple API calls triggered then any one which response last is getting is updating state.
Where i want to use only last called API response.
Exp : call API 3 times with different postbody, in case first call response take time than 2nd & 3rd then callback is using response of 1st call to setstate, instead i want to forget 1 and second call and consider last call response only.
Following is example
Common File
const apiService = axios.create({
baseURL: 'https://example.com/api/,
});
function post(postData) {
return Promise.resolve(apiService.post('https://example.com/api/getuserlist', postData, {
headers: {'Authorization': `Bearer sdfsdfsdf-cvdfs`}
}));
}
Service File
static getUsers(postData) {
return post(postData);
}
React Component
function getUsersList = (Filters )=>{
getUsers({ "Filters": Filters }).then((response) => {
this.setState({ users: response.data})
})
}
Problem is when getUsersList get called multiple time whichever is last response is getting set to users state, where last call should be users list.
It's not yet possible to actually cancel promises in JavaScript (though there is a proposal for it).
However, it is possible to implement the functionality you want in React. Consider something like this:
// state shape:
// {
// loading: boolean
// apiPromise: null | Promise<{ data: User[] }>
// users: User[]
// }
getUsersList = async (Filters) => {
// raw promise - don't await it yet
const apiPromise = getUsers({ Filters })
this.setState({ apiPromise, loading: true })
// _here_ we await it
const response = await apiPromise
// check if it's the most recent API request we made
if (this.state.apiPromise === apiPromise) {
this.setState({ users: response.data, loading: false })
} else {
console.log("stale val discarded:", users)
}
}
CodeSandbox demo - simplified example with mocked API and single val rather than list of users. Try clicking the button many times in quick succession and watch the console output.
Using CPromise wrapper the code might look like this See the live demo:
const CPromise = require('c-promise2');
const axios= require('axios');
// Let's wrap axios get method to the CPromise
function get(url){
return new CPromise((resolve, reject, {onCancel})=>{
axios.get(url, {
cancelToken: new axios.CancelToken(function executor(cancel) {
onCancel(cancel)
})
}).then(resolve, reject);
});
}
let chain= null;
function makeRequest(url){
chain && chain.cancel();
chain= get(url).then((response)=> {
console.log(`Response ${JSON.stringify(response.data)}`);
}, function (err) {
console.warn(`Error: ${err}`);
}
);
}
// some endpoint with a delay of 3 seconds for a response
const url= "https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=3s";
makeRequest(url);
// make the same request again, abort the previous
setTimeout(()=> makeRequest(url), 1000);
But since the package is in early beta stage, you can use the plain cancellation token, provided by axios See Axios documentation
I want to test this redux action which uses axios, I have succesfully tested the success path but I can't reach the catch block to complete the code coverage. I'm mocking axios using a mocks folder in jest.
export const fetchTasks = () => dispatch => {
dispatch(fetchLoading());
axios
.get(url + '/all', { mode: 'cors' })
.then(response => {
dispatch(setTasks(response.data));
dispatch(fetchDone());
})
.catch(response => {
console.log(response);
dispatch(fetchDone());
});
};
this is my success path test which implements a redux store and expects loading and setTasks to run and the setTasks action to have a payload that matches my mockObjects tasks list.
describe('when fetchTasks is dispatched', () => {
it('should dispatch other actions with correct payloads', async () => {
const store = testStore();
const spyOnDispatch = jest.spyOn(store, 'dispatch');
await tasksActions.fetchTasks()(store.dispatch, store.getState);
expect(spyOnDispatch).toHaveBeenCalledWith({ type: 'FETCH_LOADING' });
expect(spyOnDispatch).toHaveBeenCalledWith({ type: 'SET_TASKS', tasks: mockObjects.mockTasks });
expect(spyOnDispatch).toHaveBeenCalledWith({ type: 'FETCH_DONE' });
expect(store.getState().tasksState.tasks).toEqual(mockObjects.mockTasks);
});
});
I've tried to use this code to make the catch code to run but it executes the same code as the success path and sets the tasks list to undefined
mockAxios.get.mockImplementationOnce(() => Promise.reject({ status: 400 }));
When testing frontend code that makes HTTP request, I suggest using a mocking library to create fake responses. Two that I am familar with are nock and fetch-mock. You should be able to throw an error in your mock response code so that it triggers the catch() branch.
I started writing unit tests for my nodeJs application so i could learn about this concept.
After writing some basic tests for simple functions (using Mocha and Chai) i want to move on to some more complex tests.
I have written a simple piece of code that can make a request using node's HTTPS module. That code looks like this:
const https = require('https')
module.exports.doRequest = 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()
})
}
Now i want to invoke this method with the following parameters:
const PARAMS = {
host: 'jsonplaceholder.typicode.com',
port: 433,
method: 'GET',
path: `/todos/1`,
headers: {
Authorization: 'Bearer 123'
}
}
And make the request like so:
getTodos = (PARAMS) => {
return doRequest(PARAMS).then((result) => {
if (result.errors) { throw result }
return {
'statusCode': 200,
'body': JSON.stringify({ message: result.title }),
}
}).catch((error) => ({
'statusCode': error.statusCode,
'body': JSON.stringify({ message: error.message }),
}
))
}
Now my question is how i can test this bit of code properly. I have looked on how to tackle this with the Nock.js libary but i don't have a good understanding on where to start.
If anyone can point me in the right direction on how to start with writing some tests for this bit of code i will be thankfull.
In general, you would want to black box your HTTP handling, so that as few modules in your application need to care about the details of HTTP as possible.
In the source folder, you'd have one module (e.g. commonhttp.js). You want this to export your HTTP functions, and other modules in your application use them like this:
const commonhttp = require('./commonhttp');
commonhttp.doRequest( ... ).then( ... );
Other modules, like todos.js, and various other modules, will export their own functions using that module, for example:
const commonhttp = require('./commonhttp');
const todos = {
getTodos( ... ) {
return commonhttp.doRequest( ... );
},
createTodo( ... ) {
return commonhttp.doRequest( ... );
},
// etc.
};
module.exports = todos;
For your unit tests, when you test the todos.js module, you want to mock any calls to the commonhttp module; you can use simple mocha + Sinon for this, and spy on the doRequest method. Basically all you're testing is "when I call getTodos, I expect it to make a call to doRequest with these arguments". You'd follow this pattern for all the modules in your application that uses doRequest.
You also, of course, want to test the commonhttp module -- that spec is where Nock might come in handy. It's not strictly necessary, you can also "block-box" the http module, but you have to set up a lot of complicated spies to mimic the behavior of http; instead, writing a spec (using Nock) that says "ok, I call doRequest with these params, that should have made this HTTP call" does make sense.
I'm trying to send a CORS POST request to my API and it throws a TypeError every time I use the 'Authorization' header. The request doesn't even get sent, so the server is not involved. But this only happens in my tests. When I try it in Chrome it works just fine.
Here is the function that I'm testing:
export const postNewEmployee = formData => {
return fetch('http://localhost:3003', {
method: 'POST',
headers: {
'Authorization': 'Bearer test123',
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response)
.catch(error => {
throw error;
});
};
And its test:
import * as API from './api';
describe('postNewEmployee', () => {
it('posts the form data asynchronously', () => {
let formData = {
employee: {
name: 'Test Person',
email: 'test#person.nu',
address: 'an adress 123'
}
};
return API.postNewEmployee(formData)
.then(json => {
expect(json.status).toEqual(201);
}).catch(error => {
console.log(error);
});
});
});
The application is a react/redux app created with create-react-app, so I'm using Jest and JSDOM to test this. The thing is, if I comment out the Authorization header from the fetch()-call, it works fine. But if I add that header I get this:
TypeError: Cannot convert undefined or null to object
at Object.getRequestHeader (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xhr-utils.js:20:23)
at setDispatchProgressEvents (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:909:38)
at XMLHttpRequest.send (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:700:11)
at /Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/whatwg-fetch/fetch.js:429:11
at Object.<anonymous>.self.fetch (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/whatwg-fetch/fetch.js:373:12)
at Object.<anonymous>.exports.postNewEmployee.formData [as postNewEmployee] (/Users/johanh/Kod/react-app/src/api/api.js:20:10)
at Object.it (/Users/johanh/Kod/react-app/src/api/api.test.js:75:16)
at Object.<anonymous> (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/build/jasmine-async.js:42:32)
at attemptAsync (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/vendor/jasmine-2.4.1.js:1919:24)
at QueueRunner.run (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/vendor/jasmine-2.4.1.js:1874:9)
And as I said, this only happens in the test. In the browser it works fine.
I feel like I'm missing something obvious here, but I just can't see it. I've looked in the fetch spec and the jsdom documentation, but to no avail. Any ideas?
Normally you should not make real requests in a unit test. The best way to handle this is to use a mock instead of the real fetch implementation.
I assume you are using the JS implementation of fetch. So you can set fetch to what ever you want in your test.
import * as API from './api';
describe('postNewEmployee', () => {
it('posts the form data asynchronously', () => {
// set fetch to a mock that always returns a solved promise
const fetch = jest.fn((url, options) => return Promise.resolve({status: 201}))
global.fetch = fetch;
let formData = {
employee: {
name: 'Test Person',
email: 'test#person.nu',
address: 'an adress 123'
}
};
//test that fetch was called with the correct parameters
expect(fetch.mock.calls[0][0]).toBe('http://localhost:3003')
expect(fetch.mock.calls[0][1]).toEqual(formData)
return API.postNewEmployee(formData)
.then(json => {
expect(json.status).toEqual(201);
}).catch(error => {
console.log(error);
});
});
});