Tests hang in the testcafe browser when you try to test a rest api url directly.
I am trying to run a test against my rest API endpoint using request hooks, but when I run the test from the command line, the browser opens the API endpoint and loads it and hangs. The test doesn't pass or fail and hangs. The rest API endpoint just returns a JSON response.
const logger = RequestLogger('https://example.com/search/suggestions?search=testkeyword');
fixture `test`
.page('https://example.com/search/suggestions?search=testkeyword');
test
.requestHooks(logger)
('test', async t => {
// Ensure that the response has been received and that its status code is 200.
await t.expect(logger.contains(record => record.response.statusCode === 200)).ok();
const logRecord = logger.requests[0];
console.log(logRecord.userAgent);
console.log(logRecord.request.url);
console.log(logRecord.request.method);
console.log(logRecord.response.statusCode);
});
I expect the test to pass checking for 200 status code, but the test hangs without showing pass/fail. Does testcafe support testing of rest API endpoints? I have checked this issue - https://github.com/DevExpress/testcafe/issues/1471 where it says testcafe doesn't support non-html pages. Please confirm.
You are right, TestCafe is intended to work with html pages, but it will use the "about:blank" page if you don't define any url. You can use the regular node.js HTTP API without request hooks for this case. Look at the following example:
import http from 'http';
const getResponseData = (url) => new Promise((resolve, reject) => {
http.get(url, res => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => resolve({ statusCode, contentType, rawData }));
}).on('error', e => reject(e));
});
fixture `Test REST API`;
test('Simple Test', async t => {
const response = await getResponseData('http://example.com/api/1');
await t
.expect(response.statusCode).eql(200);
});
TestCafe 1.20.0+ offers the t.request method. You can use it for REST API testing. In other words, you can incorporate API testing right into your existing functional TestCafe tests. You no longer need to use any third-party libraries.
You can read about the API testing feature in the corresponding guide.
Related
I'm struggling to perform a test with jest concerning an axios api call
here is my API call, that works perfectly within my program
import axios from 'axios';
import crypto from 'crypto';
import { prop } from 'ramda';
const baseUrl = 'http://gateway.marvel.com:80';
const uri = '/v1/public/characters';
const charactersUrl = baseUrl + uri;
const timestamp = [Math.round(+new Date() / 1000)];
const privateApi = 'XXX';
const publicApi = 'XXX';
const concatenatedString = timestamp.concat(privateApi, publicApi).join('');
const hash = crypto.createHash('md5').update(`${concatenatedString}`).digest('hex');
const charactersApi = () =>
axios
.get(charactersUrl, {
params: {
ts: timestamp,
apikey: publicApi,
hash,
},
})
.then(prop('data'));
export default charactersApi;
When I'm trying to test it, that way:
import axiosMock from 'axios';
import charactersApi from '../marvelApi';
jest.mock('axios', () => ({
get: jest.fn(),
}));
describe('tools | marvelApi', () => {
const piece = { name: '3D-MAN' };
axiosMock.get.mockResolvedValueOnce({ data: piece });
it('should get the character', () => {
return charactersApi().then(elem => {
expect(elem.name).toEqual('3D-MAN');
});
});
});
I get the following message from jest
TypeError: Cannot read property 'then' of undefined
16 |
17 | const charactersApi = () =>
> 18 | axios
| ^
19 | .get(charactersUrl, {
20 | params: {
21 | ts: timestamp,
at charactersApi (src/tools/marvelApi.js:18:3)
at Object.<anonymous> (src/tools/tests/marvelApi.test.js:13:12)
What I have tried
A common error is to forget the return statement within the function that contain the request API, in my case it's done correctly (first piece of code -> charactersApi()) source1, source2
I also tried to return a Promise from the mocked Axios as I have seen on another SO ticket
jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve()),
}));
I think my axios mock is not correct, because the struggle comes from the test while the production version work well
Any thoughts ?
You can spy on the "axios.get" calls and resolve them to a fixed (mocked) value:
/**
* #jest-environment jsdom
*/
const axios = require('axios')
beforeAll(() => {
jest.spyOn(axios, 'get').mockImplementation()
})
afterAll(() => {
jest.restoreAllMocks()
})
it('returns the mocked response', async () => {
axios.get.mockResolvedValue({ data: 'foo' })
const res = await axios.get('https://api.github.com')
expect(res).toEqual({ data: 'foo' })
})
You shouldn't use jest.mock because it mocks a module that your imported code may be using. As far as I know, it doesn't affect the current module's imports (and you import axios as a part of your test).
Recommended solution
I strongly discourage you from spying/mocking axios directly. See my argumentation below.
You're mocking implementation details of axios. In other words, you take the axios.get function and throw it away, alongside any internal logic it may have, and replace it with a hard mock. This means your test no longer uses axios, instead it uses an emptied mocked shell of axios. This makes your test different from your actual code, which, in turn, decreases the confidence such a test gives you.
You're coupling your mocks with a specific request client (axios). Such an approach is not a long-term investment, as you're writing axios-specific mocks. You can't reuse such mocks for requests made by other clients (i.e. window.fetch, Apollo, etc.), because they have their own implementation details (i.e. window.fetch has no .get() to spy on), which only encourages you to write more implementation-specific logic in tests.
You can learn more about the disadvantages of direct mocking of request clients in the Stop mocking fetch article by Kent C. Dodds. It uses window.fetch mocks as an example, but you may replace it with ANY_REQUEST_CLIENT when reading.
I highly recommend using tools like Mock Service Worker (MSW) that will encourage you to write abstracted mocks that don't rely on any request clients (you can use them no matter how your tested code makes a request) and can even be reused across different testing levels (the same mocks for Jest, Storybook, or Cypress).
Here's how your test would look like with MSW:
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import charactersApi from '../marvelApi';
const server = setupServer(
rest.get('http://gateway.marvel.com:80/v1/public/characters', (req, res, ctx) => {
return res(ctx.json({
data: {
name: '3D-MAN'
}
}))
})
)
beforeAll(() => server.listen()
afterAll(() => server.close())
describe('tools | marvelApi', () => {
it('should get the character', () => {
return charactersApi().then(elem => {
expect(elem.name).toEqual('3D-MAN')
})
})
})
Notice how there are no details about how the request is made, only which request to intercept and mock its response.
You can follow a detailed tutorial on how to Get started with MSW. There's also a great video on API mocking and what problems MSW solves.
I am trying to access the Uber API with Axios and I am running into some trouble. I have plugged this data into Postman and I get a 200 response code with no problems. However, when I try to make an Axios call, I get response code 401 unauthorized. Can I get some help looking through my code to find out why my authorization is not working correctly with Axios?
Here is a link to the Uber API docs I am referencing. Uber API Reference
getRide_Uber = async (addressOrigin, addressDestination) => {
let origin = await geocodeAddress(addressOrigin);
let destination = await geocodeAddress(addressDestination);
const url = "https://api.uber.com/v1.2/estimates/price";
const params = {
params: {
start_latitude: origin.lat,
start_longitude: origin.lon,
end_latitude: destination.lat,
end_longitude: destination.lon
}
};
const headers = {
headers: {
Authorization: `Token ${process.env.UBER_SERVER_TOKEN}`
}
};
const response = await axios
.get(url, params, headers)
.then(function(response) {
data = response.data;
})
.catch(function(error) {
console.log(error);
});
return data;
};
Please let me know if anything needs clarification. Thanks!
try below syntax,
const config = {
headers: {
Authorization: `Token ${process.env.UBER_SERVER_TOKEN}`
}
params: {
start_latitude: origin.lat,
start_longitude: origin.lon,
end_latitude: destination.lat,
end_longitude: destination.lon
}
};
const response = await axios
.get(url, config)
.then(function(response) {
data = response.data;
})
.catch(function(error) {
console.log(error);
});
return data;
There is one more aspect axios, async/await is not supported in Internet Explorer and older browsers. So also please check your browser versions as well.
Not sure how are you getting token from env but seems the server token is not getting pass correctly, may be few extra characters while reading from env. Try to run the program first with hard coded token in program itself and once you are sure its not code issue, you can move it into config/env and then debug env read issue.
I am looking for a way to use headless chrome similar to what chromeless does but instead of being implemented as a nodejs endpoint, allowing restful requests with the html content as a payload.
I want to run this service on aws lambda being triggered through API Gateway. Does anyone have experience with this usecase?
There's nothing keeping you from using Chromeless in your use-case. Chromeless can be used within an AWS Lambda function. You can take a (RESTful) request coming from AWS API Gateway and then do something with it and Chromeless. You can combine the #serverless-chrome/lambda package with Chromeless to get headless Chrome running within Lambda so that Chrome is available to Chromeless. The Chromeless Proxy works in a similar way. For example, your Lambda function's code might look like (this is untested code I just cobbled together, but should convey the idea):
const launchChrome = require('#serverless-chrome/lambda')
const Chromeless = require('chromeless').Chromeless
module.exports.handler = function handler (event, context, callback) {
const body = JSON.parse(event.body) // event.body coming from API Gateway
const url = body.url
const evaluateJs = body.evaluateJs
launchChrome({
flags: ['--window-size=1280x1696', '--hide-scrollbars'],
})
.then((chrome) => {
// Chrome is now running on localhost:9222
const chromeless = new Chromeless({
launchChrome: false,
})
chromeless
.goto(url)
.wait('body')
.evaluate(() => `
// this will be executed in headless chrome
${evaluateJs}
`)
.then((result) => {
chromeless
.end()
.then(chrome.kill) // https://github.com/adieuadieu/serverless-chrome/issues/41#issuecomment-317989508
.then(() => {
callback(null, {
statusCode: 200,
body: JSON.stringify({ result })
})
})
})
.catch(callback)
})
.catch((error) => {
// Chrome didn't launch correctly
callback(error)
})
}
You'll find a similar thread on the Chromeless Issue tracker here.
Disclosure: I'm a collaborator/author of these packages.
I am busy with a angular4 web app that uses the youtubeInMp3 API. Everything works as it should when downloading mp3 files using the API however it doesn't work for all videos, I'm not sure which videos give the issue, I've noticed that it often happens when the YouTube video ID contains the _ character. when it does I get the below error... I know that the issue isn't with the video as it works when I test it http://www.youtubeinmp3.com I've tried urlEncoding the id before passing it to the API, replacing _ with - and replacing _ with %20 with no success. Any idea what might be going wrong here/how I can go about fixing it?
My error (from chrome developer console)
core.es5.js:3046 Angular is running in the development mode. Call
enableProdMode() to enable the production mode.
main.bundle.js:368 id: t0sS60Jx51c main.bundle.js:788 url
main.bundle.js:789
http://www.youtubeinmp3.com/fetch/?format=JSON&video=https://www.youtube.com/watch?v=t0sS60Jx51c
main.bundle.js:376 SyntaxError: Unexpected token < in JSON at position
0
at Object.parse ()
at Response.Body.json (vendor.bundle.js:37736)
at MapSubscriber.project (main.bundle.js:791)
at MapSubscriber._next (vendor.bundle.js:22171)
at MapSubscriber.Subscriber.next (vendor.bundle.js:242)
at XMLHttpRequest.onLoad (vendor.bundle.js:38165)
at ZoneDelegate.webpackJsonp.576.ZoneDelegate.invokeTask (polyfills.bundle.js:2496)
at Object.onInvokeTask (vendor.bundle.js:5501)
at ZoneDelegate.webpackJsonp.576.ZoneDelegate.invokeTask (polyfills.bundle.js:2495)
at Zone.webpackJsonp.576.Zone.runTask (polyfills.bundle.js:2263)
When I make this request that gives me an error in the insomnia REST client I get the following response:
metahttp-equiv="refresh" content="0" url="http://www.youtubeinmp3.com/download/?video=https://www.youtube.com/watch?v=t0sS60Jx51cautostart=1n=x="
An example of a response successful request to the youtubeInMp3 Api, the id logged in the console is the YouTube video id:
id: bjLorSJAB70 main.bundle.js:788 url main.bundle.js:789
http://www.youtubeinmp3.com/fetch/?format=JSON&video=https://www.youtube.com/watch?v=bjLorSJAB70
main.bundle.js:371 Objectlength: "199"link:
"http://www.youtubeinmp3.com/download/get/?i=fOzuoPTm06oBoLw3aidWejXF7Yc2JB3T&e=33"title:
"Santo Daime EU CHAMO A CURA"proto: Object main.bundle.js:373
http://www.youtubeinmp3.com/download/get/?i=fOzuoPTm06oBoLw3aidWejXF7Yc2JB3T&e=33
My code:
//function that passes the video id to a restService for downloading:
download(id: string) {
console.log("id: " + id);
this.restService.yDownload(id)
.subscribe(
(res) => {
console.log(res);
let url = res.link;
console.log(url);
window.location.href = `${url}target="_blank"`;
}, (res) => {
console.log(res)
});
}
//rest service function for getting the download link from youtubeInMp3 api
yDownload(id: string) : Observable<any> {
let params = [
`format=JSON`,
`video=https://www.youtube.com/watch?v=${id}`
].join('&');
let queryUrl = `${this.Y_DOWNLOAD_URL}?${params}`;
console.log("url");
console.log(queryUrl);
return this.http.get(queryUrl)
.map((res: Response) => res.json())
.catch((err: any) => Observable.throw(err || 'server error'));
}
I am using axios for a react application and I would like to log all axios calls that I'm making anywhere in the app. I'm already using a single global instance of axios via the create function and I am able to log a generic console.log. However I would like more info like function being called, parameters, etc.
The best way to do this would be an interceptor. Each interceptor is called before a request/response. In this case a logging interceptor would be.
axios.interceptors.request.use(request => {
console.log('Starting Request', JSON.stringify(request, null, 2))
return request
})
axios.interceptors.response.use(response => {
console.log('Response:', JSON.stringify(response, null, 2))
return response
})
or something to that effect.
It's good that you're using a new instance of axios:
const api = axios.create({
timeout: 1000
})
That way you can call
api.interceptors[...]
Use axios-debug-log
npm install --save axios-debug-log
require('axios-debug-log') before any axios call
Set the environment variable DEBUG=axios
By default, you'll see logs like the following:
axios POST /api/auth/login +0ms
axios 200 (POST http://localhost:8080/api/auth/login) +125ms
axios POST /api/foo +0ms
axios 200 (POST http://localhost:8080/api/foo) +15ms
Refer to the docs for configuration and customization options.
It looks like you can intercept all requests using an "interceptor", and log inside of it: https://github.com/mzabriskie/axios#interceptors
Use axios-logger
When you send a request in nodejs, you need to show the log to the console.
You can try wrap the axios.request function in a Promise.
function loggedRequest(config) {
return new Promise((resolve, reject) => {
axios.request(config)
.then((res) => {
// log success, config, res here
resolve(res);
})
.catch(err => {
// same, log whatever you want here
reject(err);
})
})
}
Here's an NPM package for MySQL that let's you log all axios requests https://www.npmjs.com/package/axios-logger-mysql , I hope this helps.