Undici mocking won't intercept my request in a jest test - javascript

I am trying to upgrade my node project to use version 18 and the built in fetch vs our old method of using node-fetch. We were writing tests using nock, which isn't an option with this version of fetch. I am not able to get undici to intercept the request. I am getting the response as if I went to the page (google.com/test)
I created a standalone test file and that didn't work either.
const { MockAgent, setGlobalDispatcher, } = require('undici');
test('should respond with body passed in', async () => {
const opts = { method: 'GET' };
const body = { statusInfo: { status: 'SUCCESS' }};
const agent = new MockAgent({ connections: 1 });
agent.get('http://www.google.com').intercept({
path: '/test'
}).reply(200, body);
agent.disableNetConnect();
agent.activate();
setGlobalDispatcher(agent);
const result = await fetch('http://www.google.com/test', opts);
console.log(JSON.stringify(result));
// console.log('%o', result);
console.log(await result.text());
// expect(result).toEqual(body);
});

Related

(intermediate value).get is not a function

I am trying to integrate an API with my angular project which displays the internet speed of my connection. this is an api which is provided by fast.com - https://www.npmjs.com/package/fast-speedtest-api I am trying to follow just as how it is being mentioned in the doc of NPM package. The error that I am receiving is
app.component.ts:18 (intermediate value)(intermediate value)(intermediate value).get is not a function
As per what i understood on google is it must be a syntax error but i dont find any errors in my typescript file. below is my typescript code
const https = require('https');
const http = require('http');
const Timer = require('./Timer');
const ApiError = require('./ApiError');
const FastSpeedtest = require("fast-speedtest-api");
let speedtest = new FastSpeedtest({
token: "YXNkZmFTOKENoYXNkZmhrYWxm", // required
verbose: false, // default: false
timeout: 10000, // default: 5000
https: true, // default: true
urlCount: 5, // default: 5
bufferSize: 8, // default: 8
unit: FastSpeedtest.UNITS.Mbps // default: Bps
});
speedtest.getSpeed().then((s :any) => {
console.log(`Speed: ${s} Mbps`);
}).catch((e :any) => {
console.error(e.message);
});
just in case, i have also checked the async method that was there from the npm package itself. I do not find an error in there as well.
/**
* Get data from the specified URL
*
* #async
* #param {string} url The URL to download from
* #return {Promise} The request and response from the URL
*/
async get(url) {
return new Promise((resolve, reject) => {
const request = (this.https ? https : http).get(url, (response) => {
if (response.headers['content-type'].includes('json')) {
response.setEncoding('utf8');
let rawData = '';
response.on('data', (chunk) => {
rawData += chunk;
});
response.on('end', () => {
const parsedData = JSON.parse(rawData);
response.data = parsedData;
resolve({
response,
request
});
});
} else {
resolve({
response,
request
});
}
}).on('error', (e) => {
reject(e);
});
});
}
what might be the issue?
fast-speedtest-api is meant to be used on nodejs not an angular app !
The problem is this:
(this.https ? https : http).get(...)
You try to resolve what to call the get on, but with wrong scoping. this.https != https. Since the package https is an import, it won't be on the this scope.
I do wonder however, what the point of this is. Is using http a fallback for when the https package can't be found? What is it what you're trying to check here?
As a sidenote, debugging (and reading the code, for that matter) would be easier if you do something like
const resolver = https ? https : http;
resolver.get(...)

How to mock a function using Frisby and Jest to return custom response?

I'm trying to mock a function using Frisby and Jest.
Here are some details about my code:
dependencies
axios: "^0.26.0",
dotenv: "^16.0.0",
express: "^4.17.2"
devDependencies
frisby: "^2.1.3",
jest: "^27.5.1"
When I mock using Jest, the correct response from API is returned, but I don't want it. I want to return a fake result like this: { a: 'b' }.
How to solve it?
I have the following code:
// (API Fetch file) backend/api/fetchBtcCurrency.js
const axios = require('axios');
const URL = 'https://api.coindesk.com/v1/bpi/currentprice/BTC.json';
const getCurrency = async () => {
const response = await axios.get(URL);
return response.data;
};
module.exports = {
getCurrency,
};
// (Model using fetch file) backend/model/cryptoModel.js
const fetchBtcCurrency = require('../api/fetchBtcCurrency');
const getBtcCurrency = async () => {
const responseFromApi = await fetchBtcCurrency.getCurrency();
return responseFromApi;
};
module.exports = {
getBtcCurrency,
};
// (My test file) /backend/__tests__/cryptoBtc.test.js
require("dotenv").config();
const frisby = require("frisby");
const URL = "http://localhost:4000/";
describe("Testing GET /api/crypto/btc", () => {
beforeEach(() => {
jest.mock('../api/fetchBtcCurrency');
});
it('Verify if returns correct response with status code 200', async () => {
const fetchBtcCurrency = require('../api/fetchBtcCurrency').getCurrency;
fetchBtcCurrency.mockImplementation(() => (JSON.stringify({ a: 'b'})));
const defaultExport = await fetchBtcCurrency();
expect(defaultExport).toBe(JSON.stringify({ a: 'b'})); // This assert works
await frisby
.get(`${URL}api/crypto/btc`)
.expect('status', 200)
.expect('json', { a: 'b'}); // Integration test with Frisby does not work correctly.
});
});
Response[
{
I hid the lines to save screen space.
}
->>>>>>> does not contain provided JSON [ {"a":"b"} ]
];
This is a classic lost reference problem.
Since you're using Frisby, by looking at your test, it seems you're starting the server in parallel, correct? You first start your server with, say npm start, then you run your test with npm test.
The problem with that is: by the time your test starts, your server is already running. Since you started your server with the real fetchBtcCurrency.getCurrency, jest can't do anything from this point on. Your server will continue to point towards the real module, not the mocked one.
Check this illustration: https://gist.githubusercontent.com/heyset/a554f9fe4f34101430e1ec0d53f52fa3/raw/9556a9dbd767def0ac9dc2b54662b455cc4bd01d/illustration.svg
The reason the assertion on the import inside the test works is because that import is made after the mock replaces the real file.
You didn't share your app or server file, but if you are creating the server and listening on the same module, and those are "hanging on global" (i.e: being called from the body of the script, and not part of a function), you'll have to split them. You'll need a file that creates the server (appending any route/middleware/etc to it), and you'll need a separate file just to import that first one and start listening.
For example:
app.js
const express = require('express');
const { getCurrency } = require('./fetchBtcCurrency');
const app = express()
app.get('/api/crypto/btc', async (req, res) => {
const currency = await getCurrency();
res.status(200).json(currency);
});
module.exports = { app }
server.js
const { app } = require('./app');
app.listen(4000, () => {
console.log('server is up on port 4000');
});
Then, on your start script, you run the server file. But, on your test, you import the app file. You don't start the server in parallel. You'll start and stop it as part of the test setup/teardown.
This will give jest the chance of replacing the real module with the mocked one before the server starts listening (at which point it loses control over it)
With that, your test could be:
cryptoBtc.test.js
require("dotenv").config();
const frisby = require("frisby");
const URL = "http://localhost:4000/";
const fetchBtcCurrency = require('./fetchBtcCurrency');
const { app } = require('./app');
jest.mock('./fetchBtcCurrency')
describe("Testing GET /api/crypto/btc", () => {
let server;
beforeAll((done) => {
server = app.listen(4000, () => {
done();
});
});
afterAll(() => {
server.close();
});
it('Verify if returns correct response with status code 200', async () => {
fetchBtcCurrency.getCurrency.mockImplementation(() => ({ a: 'b' }));
await frisby
.get(`${URL}api/crypto/btc`)
.expect('status', 200)
.expect('json', { a: 'b'});
});
});
Note that the order of imports don't matter. You can do the "mock" below the real import. Jest is smart enough to know that mocks should come first.

Jest global setup leads to inconsistent test pass/failure

I'm trying to test an API with jest. Originally all my tests wherein one file and all tests where passing. I wanted to separate my tests into different files. To do this I'm trying to use a global setup file with beforeEach and afterEach. The problem is that sometimes I run the tests and they pass and other times they fail (different test pass and different fail each time).
package.json
{
"jest": {
"verbose": true,
"bail": false,
"preset": "#shelf/jest-mongodb",
"setupFilesAfterEnv": [
"<rootDir>/jest.setup.js"
],
"globals": {
"authHeaders": {},
"authId": null
}
},
}
jest.setup.js
const app = require("./index")
const supertest = require("supertest")
const request = supertest(app)
const db = require("./src/db")
const jwtDecode = require("jwt-decode")
const getAuthToken = require("./src/utils/getAuthToken")
let token
let decodedJwt
const getAuthHeaders = token => ({
Authorization: `Bearer ${token}`
})
beforeEach(async done => {
//setup globals available in all tests
token = await getAuthToken()
token = JSON.parse(token)["access_token"]
decodedJwt = jwtDecode(token)
authId = decodedJwt.sub
authHeaders = getAuthHeaders(token)
// Ensure that we have a users collection with our test user
// who can then call the api
await request
.post("/v1/users")
.set(authHeaders)
.send({ email: "test#example.com" })
.expect(200)
done()
})
afterEach(async done => {
// Delete the users collection
await db.instance.dropUsers()
await db.instance.dropAgencies()
done()
})
users.test.js
const app = require("../../../../index")
const supertest = require("supertest")
const request = supertest(app)
const faker = require("faker")
describe("/v1/users/:id", () => {
test("should return an error when the users do not have a mutual agency", async done => {
if (!authHeaders || !authId) {
expect(true).toBe(false)
}
let testUserId = null
let email = faker.internet.email()
await request
.post("/v1/users")
.set(authHeaders)
.send({ email })
.expect(200)
.then(({ body }) => (testUserId = body._id))
await request
.get("/v1/users/" + testUserId)
.set(authHeaders)
.expect(200)
.then(({ body }) => {
expect(body).toHaveProperty("error")
expect(body.error).toHaveProperty("message")
})
done()
})
})
I can run the above once and it will pass and run again moments later and it will fail.
Edit:
After further investigation it seems that the issue is something to do with the afterEach function not dropping the Users collection. When I console log the response of the request made in the beforeEach in the jest.setup.js file I'm getting an error response saying that the user for the given email already exists.
The issue was caused by multiple tests running at the same time, adding the --runInBand flag (which forces test to be run serially) fixes the issue.
The error in my thinking was I assumed it was safe to add a user record to the database beforeEach and delete it afterEach without considering that if two tests run at the same time one would always fail as a user would already exist in the database.
In light of this I think it would be an idea to refactor my setup file so that my test user is only created once for all tests but I need to consider the implications of doing this.
EDIT:
I have found that I can run test successfully without the runInBand flag by switching out my atlas cloud database in favour of mongodb-memory-server during tests.

FeathersJS: Injecting HTTP headers in Service Test

In a feathersJS service, I have a before hook being ran that expects a certain HTTP header to exist:
src/services/service_name/service_name.hooks.js
const validationHook = () => (context, next) => {
if (!context.params.headers.hasOwnProperty('header-wanted'))
throw new errors.BadRequest();
next(null, context);
};
module.exports = {
before: {
all: [cronValidationHook()],
...
..
.
When testing this service in a generated test file from feathers-cli, however, I haven't found a way to inject headers prior to the before hook being called. The test in question is:
test/services/service_name.test.js
describe('get', () => {
it('should run "id" endpoint', async () => {
const service = app.service('v1/cron');
const resp = await service.get('id', params);
// Assertions exist after this call
});
});
Is there a way to do this that does not require utilizing an HTTP call via node-fetch or requests?
params will be whatever you pass. Just set params.headers to what you would like to test, e.g.
const getParams = {
...params,
headers: { 'header-wanted': 'something' }
};
const resp = await service.get('id', getParams);

CycleJs - Unit Testing a Component with Mocha

I'm learning in cyclejs and rxjs and I'm struggling to write a unit test for one of the components I'm working on. I'm sorry for the long question. I have tried to simplify the question and this is the best I can do.
Component does the following things:
1) When it's loaded, it sends a request to an end point to retrieve user's tasks. From these tasks, it finds the task user currently working on and creates an url stream which contains the endpoint which will be called when user clicks complete task button.
2) When a new value come to test stream, it creates a new stream from tests defined so far.
3) When user clicks complete task, component first sends a request to an endpoint to add tests user defined to the task. Then, if the call is successful, component sends another request to the endpoint which is retrieved at step 1 to complete task.
4) After complete task request is completed, component shows a notification to the user.
Here is the component:
import Rx from 'rx';
import {Observable} from 'rx';
const Model = ({test$, completeTask$, sources}) => {
const processId = Number(sources.routeParams.processId);
const SET_TEST_RESULTS = `http://dummyhost/tosettests/${processId}`;
const GET_USER_TASKS = 'http://dummyhost/togetusertasks';
const taskRequest$ = Observable.just({
url: GET_USER_TASKS,
method: 'GET'
});
const url$ = sources.HTTP
.filter(response => response.request.url === GET_USER_TASKS)
.flatMap(response => response.body)
.filter(task => task.processId === processId)
.take(1)
.map(task=> {
const activitiId = task.processInstanceId;
return {
completeTask: `http://dummyhost/tocompletetask/${activitiId}`
};
});
const definedTest$ = test$
.scan((acc, curr) => {
acc.push(curr);
return acc;
}, [])
.startWith([])
.share();
const setTestResultsRequest$ = completeTask$
.withLatestFrom(definedTest$, (event, tests) => {
return {
url: SET_TEST_RESULTS,
method: 'POST',
send: {
tests
}
};
});
const setTestResultsResponse$ = sources.HTTP
.filter(response => response.request.url === SET_TEST_RESULTS);
const completeTaskRequest$ = setTestResultsResponse$
.withLatestFrom(url$, (response, url) => {
return {
url: url.completeTask,
method: 'POST'
};
});
const completeTaskResponse$ = sources.HTTP
.withLatestFrom(url$, (response, url) => {
return {
response,
url
};
})
.filter(data => data.response.request.url === data.url.completeTask)
.map((data)=> data.response);
const successNotification$ = completeTaskResponse$
.map(() => {
return {
action: 'success',
message: 'Tests have been defined!'
};
});
const request$ = Observable.merge(taskRequest$, setTestResultsRequest$, completeTaskRequest$);
return {
request$: request$,
notification$: successNotification$,
definedTest$: definedTest$
};
};
What I'm trying to unit test are the followings:
1) Component must send a request to define tests to the task
2) Component must send a request to complete task
3) Component must show a notification
Here's the unit test I wrote so far with Mocha:
describe('When complete task is clicked', ()=> {
const tests = [
{
name: 'test 1 name'
},
{
name: 'test 2 name'
},
{
name: 'test 3 name'
}
];
const test$ = Observable.fromArray(tests);
const completeTask$ = Observable.just({}).delay(300);
const processId = 3;
const activitiId = 3;
var actions;
before(done => {
const HTTPSource = new Rx.ReplaySubject();
const sources = {
DOM: mockDOMSource(),
routeParams: {processId},
HTTP: HTTPSource
};
actions = Model({test$, completeTask$, sources});
const request$ = actions.request$.share();
request$
.filter(request=> request.url === 'http://dummyhost/togetusertasks')
.subscribe((request) => {
const response = {
request,
body: [{
processId,
processInstanceId: activitiId
}]
};
HTTPSource.onNext(response);
});
request$
.filter(request=> request.url === `http://dummyhost/tosettests/${processId}`)
.subscribe((request) => {
const response = {
request
};
HTTPSource.onNext(response);
});
request$
.filter(request=> request.url === `http://dummyhost/tocompletetask/${activitiId}`)
.subscribe((request) => {
const response = {
request
};
HTTPSource.onNext(response);
done();
});
});
it('should send a request to add tests', (done)=> {
actions.request$
.filter(request=> request.url === `http://dummyhost/tosettests/${processId}`)
.subscribe(() => {
done();
});
});
it('should send a request to complete task', (done)=> {
actions.request$
.filter(request=> request.url === `http://dummyhost/tocompletetask/${activitiId}`)
.subscribe(() => {
done();
});
});
it('should show a notification to inform user that tests have been defined', (done) => {
actions.notification$.take(1)
.subscribe(notification => {
expect(notification.action).to.equal('success');
done();
});
});
});
In the test, I have tried to mock HTTP source. By subscribing request stream sink returning from component, I'm able to create mock responses for each request. But I have to use share operator to run mocha's before function successfully. Otherwise, I'm only able to mock first request which is retrieving user's tasks. So, my first question is:
Why do I have to use share operator to run mocha's before function successfully?
Currently, tests that verify complete task request is sent and notification is shown passes. But, test that verifies define test to the task request is failing. This is strange because to verify complete task request is sent, component must send define tests to the task request first. It seems define tests to the task request somehow lost in the request stream returned from component's sink. So my second question is:
Why second and third tests are passing but first test is failing?
Here's a gist that contains all code in one file:
gist link
As I said earlier, I'm new to cyclejs and rxjs. I think I'm not fully understanding what I'm testing. So any comments or answers about cyclejs and rxjs that directs me the right path are welcomed.
If you think question is not clear enough, please let me know. I'll do my best improve the question.

Categories