What api testing tools do you know and use during development? The majority will probably call it Postman. Yes, this is a fairly common and well-known tool. Someone will say that it is jMeter, Fiddler, SoapUI, Advanced REST Client.
I want to offer you Cypress. Yes, the same Cypress that web application developers use for End2end testing and less often for testing React/Vue/Angular/Svetle components. It will be especially convenient for those who create APIs on Nodejs, because Cypress uses JavaScript. Cypress is a very flexible tool that can help to automate any actions of checking both client and server code. Cypress simulates working environment for api calls as much as possible because it runs scripts for testing directly from the browser.
How to install and start working with Cypress you can easily find by this link. Now I propose to dwell on moments that are not well described on the Internet. Namely, on testing server interfaces that require authorization.
For an example of an api that needs to be implemented and tested accordingly during development, let’s take a server implementation based on Firebase Callable function and protected by Firebase Authentication (which actually is Google Identity Toolkit wrapped in Firebase). We will stop at the implementation of the api directly another time. Now we are interested in how to check it for compliance with the requirements.
Using the implementation below, it is possible to test server interfaces on behalf of any user registered in the web application.
The code is a Cypress script that tests the api endpoint getList
describe('Users API Requests', () => {
context.only('getList', () => {
it('Success / for Admin', () => {
cy.userApi("getList")
.then((response) => {
cy.log(response.body)
expect(response.status).to.eq(200)
})
})
})
cypress/e2e/backend/users-api.cy.js
The auxiliary code is designed in the form of two Cypress commands to ensure ease of programming and readability of the program code.
userApi - main command that calls getIdTokenForAdmin to get IdentityToken and produce HttpsCallable request to server, using action parameter that in our case is getList
getIdTokenForAdmin - auxiliary command to obtain IdentityToken for user userEmailAdmin. An interesting example of cache implementation. If the token was formed earlier, the repeated formation is bypassed.
Cypress.Commands.add('userApi', (action, params = {}) => {
cy.getIdTokenForAdmin().then(userIdTokenAdmin => {
return cy.request({
url: `${beUrl}/users`, ...callable({ action, ...params }, userIdTokenAdmin)
})
})
})
let userIdTokenAdmin;
Cypress.Commands.add('getIdTokenForAdmin', () => {
if (userIdTokenAdmin) return userIdTokenAdmin
return cy.task("firebase:getUserIdToken", userEmailAdmin).then(token => {
userIdTokenAdmin = token
return token
})
})
const callable = (parameters = {}, identityToken) => {
if (!identityToken) throw Error('Parameter "identityToken" is mandatory for the function callable()')
return {
method: "POST",
body: {
data: { ...parameters },
},
failOnStatusCode: false,
auth: {
bearer: identityToken
}
}
};
cypress/support/commands.js
Code that runs when Cypress starts in Nodejs (not in the browser). This is important to understand because it allows you to run firebase-admin and access the Firebase project management on the server. It is not possible to do this from the browser (where Cypress scripts are executed).
function initFirebasePlugin(on, config) {
// process.env.GOOGLE_APPLICATION_CREDENTIALS = '/Users/omnigon/projects/omnigon-site/omnigon-site-gatsby/serviceAccount.json'
// or the below
const serviceAccount = require('../../.secure/serviceAccount.json');
initializeApp({
credential: cert(serviceAccount)
});
registerFirebaseTasks(on, config)
}
function getUserIdToken(userEmail = "xxx@gmail.com") {
return getAuth().getUserByEmail(userEmail)
.then(userRecord => {
return getAuth().createCustomToken(userRecord.uid)
})
.then(customToken => {
return axios.post(
`https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=${firebaseConfigStagingApiKey}`,
{
token: customToken,
returnSecureToken: true,
},
);
})
.then(idTokenResponse => idTokenResponse.data.idToken)
}
function registerFirebaseTasks(on, config) {
on('task', {
'firebase:getUserIdToken'(email) {
return getUserIdToken(email)
}
})
}
module.exports.initFirebasePlugin = initFirebasePlugin
cypress/support/firebase-plugin.js
const { initFirebasePlugin } = require('./cypress/support/firebase-plugin');
module.exports = defineConfig({
...
setupNodeEvents(on, config) {
initFirebasePlugin(on, config)
},
});
cypress.config.js
All the code provided above is working and used in real systems.