Use Jest to test an API endpoint and its response - javascript

I want to use jest to test an API endpoint to check if it returns a response and if the JSON contains the parameter keys that I need.
My function looks like the following:
export function getFiveDayWeatherByCoordinates(id) {
let url = FORECAST_ID_URL(id);
return fetch(url)
.then(response => response.json())
.then(data => {
return data;
})
.catch(err => console.log(err));
}
It returns a JSON with a set of parameters, I will post only a snapshot:
{
cnt: 14,
cod: "200",
city: {
coord: {lat: 38.7169, lon: -9.1333},
country: "PT",
id: 8012502,
name: "Socorro",
population: 0,
timezone: 3600,
}
Every tutorial that I see until now says to mock the response, but I want to test the actual API.

I would suggest using Frisby.js to test the API responses. It's a great test framework for API testing that runs in Jest. I've used it numerous times to write API and backend integration tests. Although, I typically keep these test suites separate from my UI unit tests.
Here's an example:
it('should return weather coords', async () => {
return frisby
.get(`${global.apiUrl}/my-weather-endpoint`)
.expect('status', 200)
.expect('jsonTypes', Joi.object({
cnt: Joi.number().required(),
cod: Joi.string().required(),
city: Joi.object({
coord: Joi.object({
lat: Joi.number().required(),
lon: Joi.number().required()
}),
country: Joi.string().required(),
id: Joi.number().required(),
name: Joi.string().required(),
population: Joi.number().required(),
timezone: Joi.number().required()
}).required()
});
});
Frisby also encourages the use of the Joi validation framework (it's already included in the npm package).

Related

Is this a correct api response?

I'm trying to learn about API's and am creating a simple response when a user hit's an endpoint on my basic express app.
I'm not sure what constitutes a correct API response though, does it need to be an object?
The data I was given is an array of arrays and I export it from a file and return it in the express app as an object using response.json(), it displays ok but I'm not sure if I'm following the right approach for how I'm handling it and displaying it.
I would like to understand how should API data be returned, I had the feeling that it should always be an object but what I'm returning here is an array of arrays, some explanation to understand this better would be helpful please.
Here is my data file:
export const data = [
[
'a9e9c933-eda2-4f45-92c0-33d6c1b495d8',
{
title: 'The Testaments',
price: { currencyCode: 'GBP', amount: '10.00' },
},
],
[
'c1e435ad-f32b-4b6d-a3d4-bb6897eaa9ce',
{
title: 'Half a World Away',
price: { currencyCode: 'GBP', amount: '9.35' },
},
],
[
'48d17256-b109-4129-9274-0bff8b2db2d2',
{ title: 'Echo Burning', price: { currencyCode: 'GBP', amount: '29.84' } },
],
[
'df2555ad-7dc2-4b1f-b422-766184b5c925',
{
title: ' The Institute',
price: { currencyCode: 'GBP', amount: '10.99' },
},
],
];
Here is a snippet from my express app file which imports the data object for this endpoint:
app.get('/books', (request, response) => {
response.json({ books: data });
});
Looks okay, one thing I would do is ensure the root of the object you return always has the same name between api calls, so here your root object name is "books":
app.get('/books', (request, response) => {
response.json({ books: data });
});
I would personally change this to something like:
app.get('/books', (request, response) => {
response.json({ data: data });
});
Now that may seem confusing, but what that means is when your api is queried, the actual resources the api is returning is always in an object called data, it just builds a common pattern for where people should be expecting to see the api response resources in your JSON response.
So if you had another endpoint for authors for example, people will know the authors response will be under an object called data:
app.get('/authors', (request, response) => {
response.json({ data: authors });
});

How to import functions from another file in NodeJS?

I'm new in NodeJS, and I'm struggling a little bit on this. I'm using Express and to validate the data, I'm using Celebrate.
I've got a route.js file, where I make a POST request, using a function from another file to do so (it's the create function, from MyController. It works fine! But when I try to do the same thing to my validator, it doesn't work.
So let's take a look at the code.
The route.js file:
const express = require("express");
const MyController = require("./controllers/MyController");
const MyValidator= require("./validators/MyValidator");
const routes = express.Router();
routes.post("/path", MuValidator.validateCreate, MyController.create);
The MyValidator file:
module.exports = {
validateCreate() {
celebrate({
[Segments.HEADERS]: Joi.object({
authorization: Joi.string().required(),
}).unknown(),
[Segments.BODY]: Joi.object().keys({
userId: Joi.string().required(),
title: Joi.string().required(),
description: Joi.string().required(),
value: Joi.number().required(),
dueDate: Joi.string().required(),
}),
});
},
}
IMPORTANT:
I only get this working, if I write the validation code directly on my route, like this:
routes.post(
"/path",
celebrate({
[Segments.HEADERS]: Joi.object({
authorization: Joi.string().required(),
}).unknown(),
[Segments.BODY]: Joi.object().keys({
userId: Joi.string().required(),
title: Joi.string().required(),
description: Joi.string().required(),
value: Joi.number().required(),
dueDate: Joi.string().required(),
}),
}),
MyController.create
);
the problem is that the celebrate function creates and returns a middleware, so the middleware returned by the celebrate function must be passed as second parameter to the post but you're passing a function that execute the celebrate method instead, so validateCreate should be:
module.exports = {
validateCreate: celebrate({...})
}
I think you did something wrong with module exports
try something like this:
module.exports = {
validateCreate: function() {},
otherMethod: function() {},
};

Transferring API information from console log to bot

I am working with the Geolocation API on my discord.js bot. I have the api working properly and i receive the info i wanted into the console log but i have been unable to integrate the data i receive from the console to the actual bot embed. Any help is appreciated
var IPGeolocationAPI = require('ip-geolocation-api-javascript-sdk');
var ipgeolocationApi = new IPGeolocationAPI("8c402b955bfb4b82a7353abc99c9dca9", false);
const Discord = require('discord.js');
module.exports = {
name: 'ip',
execute(message, args) {
var inp = args[0]
// Function to handle response from IP Geolocation API
function handleResponse(json) {
console.log(json);
message.author.send(json);
/*
//create embed
const embed = new Discord.MessageEmbed()
.setTitle(`**${inp}**`)
json.forEach((data, value) => {
embed.addField(data, value)
}) //end for each
message.author.send({
embed
});
*/
} //end handle
var GeolocationParams = require('ip-geolocation-api-javascript-sdk/GeolocationParams.js');
// Get complete geolocation in English for IP address input by argument
var geolocationParams = new GeolocationParams();
geolocationParams.setIPAddress(inp);
geolocationParams.setLang('en');
ipgeolocationApi.getGeolocation(handleResponse, geolocationParams);
}
}
Errors:
for normal send: DiscordAPIError: Cannot send an empty message
for embed: json.forEach is not a function
console output:
{
ip: '123.456.789(ip removed)',
continent_code: 'NA',
continent_name: 'North America',
country_code2: 'US',
country_code3: 'USA',
country_name: 'United States',
country_capital: 'Washington',
state_prov: 'Georgia',
district: '',
city: 'Marietta',
zipcode: '30060',
latitude: '33.92530',
longitude: '-84.48170',
is_eu: false,
calling_code: '+1',
country_tld: '.us',
languages: 'en-US,es-US,haw,fr',
country_flag: 'https://ipgeolocation.io/static/flags/us_64.png',
geoname_id: '4207783',
isp: 'Total Server Solutions L.L.C.',
connection_type: '',
organization: 'Total Server Solutions L.L.C.',
currency: { code: 'USD', name: 'US Dollar', symbol: '$' },
time_zone: {
name: 'America/New_York',
offset: -5,
current_time: '2020-05-07 12:21:15.314-0400',
current_time_unix: 1588868475.314,
is_dst: true,
dst_savings: 1
}
}
the json variable in your code is an object. Rather than forEach (which is meant for arrays) try
for (var key in json){
//loop through all , pick what you want
}

Jest's expect.objectContaining() fails in expect.toHaveBennCalled()

I'm trying to write a test to vlaidate code that writes to DynamoDB with aws-sdk. Despite a very similar use case being presented in the offical docs (https://jestjs.io/docs/en/expect#expectobjectcontainingobject), my assertion fails. Any help appreciated.
This is my test:
test("givenCprRepositoryServiceTestSuite_whenSaveCprRecord_thenMetaExpiresAtAppended", async () => {
await cprRepositoryService.saveCprRecord({cprNumber: existingCpr, firstName: "Jens", lastName: "Jensen"})
expect(aws.DynamoDB.DocumentClient.prototype.put).toHaveBeenCalledWith(
expect.objectContaining({
Item: {
metaExpiresAt: expect.any(Number)
}
}))
})
And the error returned is:
Error:
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected ObjectContaining:
{"Item": {"metaExpiresAt": Any<Number>}}
Received:
{"Item": {"cprNumber": "1234567890", "firstName": "Jens", "lastName": "Jensen", "metaExpiresAt": 1570792526}, "TableName": "CountryLayer_Cpr"}
The expect.objectContaining(object) method cannot handle nested objects. To resolve this problem, I'd recommend the following modifications to your code.
test("givenCprRepositoryServiceTestSuite_whenSaveCprRecord_thenMetaExpiresAtAppended", async () => {
await cprRepositoryService.saveCprRecord({cprNumber: existingCpr, firstName: "Jens", lastName: "Jensen"})
expect(aws.DynamoDB.DocumentClient.prototype.put).toHaveBeenCalledWith(
expect.objectContaining({
Item: expect.objectContaining({
metaExpiresAt: expect.any(Number)
})
}))
})

NodeJS joi returns error = null, even though error should occur

I'm running NodeJS with joi, and are trying to validate IBM Watson IoT config object.
This is my schema:
const schema = {
body: {
config: Joi.object().keys({
org: Joi.string().required(),
type: Joi.string().required(),
id: Joi.string().required(),
domain: Joi.string().required(),
'auth-method': Joi.string().required(),
'auth-token': Joi.string().required()
}).required()
}
};
And this is my check:
Joi.validate(req.body, schema, { allowUnknown: true }, (err, value) => {
console.log(err);
});
This returns null as error which indicates no error, but this is even if I haven't POSTed any parameters to my body.
Basically I want to make sure the body of my HTTP POST contains valid JSON object like so:
config = {
"org" : "organization",
"id" : "deviceId",
"domain": "internetofthings.ibmcloud.com",
"type" : "deviceType",
"auth-method" : "token",
"auth-token" : "authToken"
};
You're not passing Joi object to the validate function, you should be passing schema.body.config instead of schema. If body and config is supposed to be part of the validation, you should wrap them with Joi.Object the same way you did for all the keys inside config.

Categories