How do I mock a specific function from a module for a specific test (Jest) - testing

I have an api.js file that contains my api call. It looks like this:
// api.js
// reusable fetch call
export const makeFetch = async (url, options) => {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`${response.status}`);
}
return await response.json();
} catch (error) {
throw new Error(`Network request failed. (error: ${error.message})`);
}
};
// actual fetch call
export const getCards = async () => {
const url = 'http://localhost:3001/api/v1/cards';
return await makeFetch(url);
};
Then I have an api.test.js file that looks like:
//api.test.js
import { makeFetch, getCards } from './api';
describe('makeFetch', () => {
// three successful tests
});
// this is where I have issues
describe('getCards', () => {
it('calls makeFetch', async () => {
await getCards();
expect(makeFetch).toHaveBeenCalledTimes(1);
});
});
this is the error I get:
FAIL src\api\api.test.js
● getCards › calls makeFetch
ReferenceError: makeFetch is not defined
at Object.it.only (src/api/api.test.js:51:15)
at new Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:182:7)
Does anyone know how to make this pass with making my previous tests fail? Thank you for your time.

You need to create a mock of makeFetch. You can do that using spyOn and mockReturnValue.
You will need to move makeFetch into its own file (lib.js) so that you can mock and replace it in your test:
// ---- lib.js ----
// reusable fetch call
export const makeFetch = async (url, options) => {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`${response.status}`);
}
return await response.json();
} catch (error) {
throw new Error(`Network request failed. (error: ${error.message})`);
}
};
// ---- api.js ----
import { makeFetch } from './lib';
// actual fetch call
export const getCards = async () => {
const url = 'http://localhost:3001/api/v1/cards';
return await makeFetch(url);
};
// ---- api.test.js ----
import * as lib from './lib';
import { getCards } from './api';
describe('makeFetch', () => {
// three successful tests
});
describe('getCards', () => {
it('calls makeFetch', async () => {
const simulatedResponse = { data: 'simulated response' };
// mock makeFetch
const mock = jest.spyOn(lib, 'makeFetch');
mock.mockReturnValue(Promise.resolve(simulatedResponse));
const result = await getCards();
expect(result).toEqual(simulatedResponse);
expect(mock).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledWith('http://localhost:3001/api/v1/cards');
// restore makeFetch
mock.mockRestore();
});
});

Related

TypeError: default is not a function when using vitest

I'm using vitest to do some unit tests in my vue application.
I've written some tests but they fail with the error message: 'TypeError: default is not a function'.
But I do not use a function called default() in my code.
import getInfo from './info';
vi.mock('axios', () => {
return {
default: {
get: vi.fn()
}
}
});
test('fn getInfo() should request api with axios.get url', async () => {
const spyAxios = vi.spyOn(axios, 'get');
await getInfo('1234');
expect(spyAxios).toHaveBeenCalledWith(`${process.env.VUE_APP_API_BASE_URL}`);
});
If I then execute npm run test the result is the following:
FAIL src/api/info/info.test.js > fn getInfo() should request api with axios.get url
TypeError: default is not a function
❯ src/api/info/info.test.js:61:22
59| test('fn getInfo() should request api with axios.get url', async () => {
60| const spyAxios = vi.spyOn(axios, 'get');
61| await getInfo('1234');
| ^
62| expect(spyAxios).toHaveBeenCalledWith(`${process.env.VUE_APP_API_BASE_URL}`);
63| });
The info.ts file looks like the following:
import { useLoginStore } from "../../store/LoginStore";
import axios from "axios";
// eslint-disable-next-line
export async function getInfo(param: string) : Promise<any> {
const loginStore = useLoginStore();
axios.defaults.headers.common = {'Authorization': `Bearer ${loginStore.accessToken}`};
const request = await axios.get(
process.env.VUE_APP_API_BASE_URL
);
if (request?.status == 200) {
return request.data;
}
else {
return null;
}
}
Does anyone know what's going on here?
The default attribute in your return object is not a function (default: {...}). Instead, you would return something like this:
vi.mock('axios', () => ({
default: () => ({
get: vi.fn(),
post: vi.fn(),
}),
}));

Jest does not continue after async method

I have an async method triggered by a click event where I make a call to an API and then process the response, like this:
async confirmName () {
const {name, description} = this.form;
const [data, error] = await Pipelines.createPipeline({name, description});
if (error) {
console.error(error);
this.serviceError = true;
return false;
}
this.idPipelineCreated = data.pipeline_id;
return true;
}
The test looks like this:
test("API success", async () => {
const ConfirmNameBtn = wrapper.find(".form__submit-name");
await ConfirmNameBtn.vm.$emit("click");
const pipelinesApi = new Pipelines();
jest.spyOn(pipelinesApi, "createPipeline").mockResolvedValue({pipeline_id: 100});
const {name, description} = wrapper.vm.form;
pipelinesApi.createPipeline().then(data => {
expect(wrapper.vm.pipelineNameServiceError).toBe(false);
wrapper.setData({
idPipelineCreated: data.pipeline_id
});
expect(wrapper.vm.idPipelineCreated).toBe(data.pipeline_id)
}).catch(() => {})
})
A basic class mock:
export default class Pipelines {
constructor () {}
createPipeline () {}
}
I'm testing a success API call and I mock the API call returning a resolved promised. The problem is the coverage only covers the first two lines of the method, not the part where I assign the response of the API call. Is this the correct approach?
Edit:
Screenshot of coverage report:
Don't mix up await and then/catch. Prefer using await unless you have very special cases (see this answer):
test("API success", async () => {
const ConfirmNameBtn = wrapper.find(".form__submit-name");
await ConfirmNameBtn.vm.$emit("click");
const pipelinesApi = new Pipelines();
jest.spyOn(pipelinesApi, "createPipeline").mockResolvedValue({pipeline_id: 100});
const {name, description} = wrapper.vm.form;
const data = await pipelinesApi.createPipeline();
expect(wrapper.vm.pipelineNameServiceError).toBe(false);
wrapper.setData({
idPipelineCreated: data.pipeline_id
});
expect(wrapper.vm.idPipelineCreated).toBe(data.pipeline_id)
expect(wrapper.vm.serviceError).toBe(false);
})

Testing a cloudflare worker with HTMLRewriter fails as its undefined

I have a test to test my cloudflare worker that looks like this:
const workerScript = fs.readFileSync(
path.resolve(__dirname, '../pkg-prd/worker.js'),
'utf8'
);
describe('worker unit test', function () {
// this.timeout(60000);
let worker;
beforeEach(() => {
worker = new Cloudworker(workerScript, {
bindings: {
HTMLRewriter
},
});
});
it('tests requests and responses', async () => {
const request = new Cloudworker.Request('https://www.example.com/pathname')
const response = await worker.dispatch(request);
console.log(response);
// const body = await response.json();
expect(response.status).to.eql(200);
// expect(body).to.eql({message: 'Hello mocha!'});
});
});
In my worker I do something like this:
const response = await fetch(BASE_URL, request);
const modifiedResponse = new Response(response.body, response);
// Remove the webflow badge
class ElementHandler {
element(element) {
element.append('<style type="text/css">body .w-webflow-badge {display: none!important}</style>', {html: true})
}
}
console.log(3);
return new HTMLRewriter()
.on('head', new ElementHandler()).transform(modifiedResponse);
Now when i run my test I get this error message:
● worker unit test › tests requests and responses
TypeError: Cannot read property 'transform' of undefined
at evalmachine.<anonymous>:1:1364
at FetchEvent.respondWith (node_modules/#dollarshaveclub/cloudworker/lib/cloudworker.js:39:17)
What seems to be wrong?
HTMLRewriter i created looks like this:
function HTMLRewriter() {
const elementHandler = {};
const on = (selector, handler) => {
if (handler && handler.element) {
if (!elementHandler[selector]) {
elementHandler[selector] = [];
}
elementHandler[selector].push(handler.element.bind(handler));
}
};
const transform = async response => {
const tempResponse = response.clone();
const doc = HTMLParser.parse(await tempResponse.text());
Object.keys(elementHandler).forEach(selector => {
const el = doc.querySelector(selector);
if (el) {
elementHandler[selector].map(callback => {
callback(new _Element(el));
});
}
});
return new Response(doc.toString(), response);
};
return {
on,
transform
};
}
Since HTMLRewriter() is called with new, the function needs to be a constructor. In JavaScript, a constructor function should set properties on this and should not return a value. But, your function is written to return a value.
So, try changing this:
return {
on,
transform
};
To this:
this.on = on;
this.transform = transform;

Jest / Supertest Error - Right-hand side of 'instanceof' is not callable

When using supertest like so,
import app from "../../src/app";
import request from "supertest";
describe("GET / - a simple api endpoint", () => {
it("Hello API Request", () => {
const result = request(app)
.get("/api/location/5eda6d195dd81b21a056bedb")
.then((res) => {
console.log(res);
})
// expect(result.text).toEqual("hello");
// expect(result.status).toEqual(200);
});
});
Im getting "Right-hand side of 'instanceof' is not callable".
at Response.toError (node_modules/superagent/lib/node/response.js:94:15)
at ResponseBase._setStatusProperties (node_modules/superagent/lib/response-base.js:123:16)
at new Response (node_modules/superagent/lib/node/response.js:41:8)
at Test.Request._emitResponse (node_modules/superagent/lib/node/index.js:752:20)
at node_modules/superagent/lib/node/index.js:916:38
at IncomingMessage.<anonymous> (node_modules/superagent/lib/node/parsers/json.js:19:7)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
status: 500,
text: `"Right-hand side of 'instanceof' is not callable"`,
method: 'GET',
path: '/api/location/5eda6d195dd81b21a056bedb'
This is just with supertest, the API works when using Postman.
Rest of the code for this call,
router.get(
"/location/:id",
(req, res) => {
locationController.getLocation(req, res);
}
);
const getLocation = async (req: Request, res: Response): Promise<void> => {
const { id } = req.params;
const location = await data.readRecord(id, Location);
res.status(location.code).json(location.data);
};
const readRecord = async (id: string, model: IModel): Promise<Response> => {
try {
const response = await model.findById(id);
if (response == null) return { code: 404, data: `ID ${id} Not Found` };
return { code: 200, data: response };
} catch (error) {
return errorHandler(error);
}
};
Is there a configuration im missing for supertest and typescript?
This approach worked,
import request = require("supertest");
import app from "../../src/app";
describe("GET/ api/location/id", () => {
it("should connect retrieve record and retrieve a code 200 and json response", async () => {
const res = await request(app)
.get(`/api/location/${id}`)
expect(res.status).toBe(200);
expect(res.body._id).toBe(`${id}`);
});
});
If you don't want to use "await" in your code , you can use "done()" in callback function.
like this.
import app from "../../src/app";
import request from "supertest";
describe("GET / - a simple api endpoint", () => {
it("Hello API Request", (done) => {
const result = request(app)
.get("/api/location/5eda6d195dd81b21a056bedb")
.then((res) => {
console.log(res);
expect(res.text).toEqual("hello");
expect(res.status).toEqual(200);
done();
//done() function means this test is done.
})
});
});
Awaiting the expect call (with Jest) worked for me.
await expect(...).rejects.toThrow()

Vue JS - How to get function result in methods()

i'm trying to use this kind of structure.
I have my axios calls in a service file and then call them in vue files.
So i have this js file
const DashboardService = {
getStationList() {
let url = '/api/stations/list'
ApiService.get(url) //ApiService is an Axios wrapper
.then(response => {
console.log(response.data) //data are logged, function is called
response.data
})
}
}
export default DashboardService
Then in the Vue File i have this:
import DashboardService from '#/_services/admindashboard.service'
export default {
methods: {
getMarkers() {
let result = DashboardService.getStationList()
console.log(result) //undefined
}},
mounted() {
this.getMarkers()
}
}
I can't understand why result is undefined because che getStationList() function gets called... when the component is mounted the functions should have returned the response... how can i solve this situation?
getStationList is an async function, so you'll need to await it's result (or use then). For example:
async mounted() {
this.markers = await DashboardService.getStationList();
},
Also see this question for more details.
Next, you are missing a return in the implementation of getStationList.
const DashboardService = {
getStationList() {
const url = '/api/stations/list';
ApiService.get(url).then(response => {
return response.data;
});
},
};
or perhaps:
const DashboardService = {
async getStationList() {
const url = '/api/stations/list';
try {
const response = await ApiService.get(url);
return response.data;
} catch (error) {
console.error(error);
return [];
}
},
};
The result is undefined because getStationList is not returning anything.
You can consider turning your api call into an async function that returns the result.
const DashboardService = {
async getStationList() {
let url = '/api/stations/list';
return ApiService.get(url);
}
}
export default DashboardService
And in your component
methods: {
async getMarkers() {
let result = await DashboardService.getStationList();
console.log(result);
}
},
If you don't want to use the async await syntax. You can return a the promise from your service and utilize the result on your component, as so:
methods: {
getMarkers() {
DashboardService.getStationList().then(result => {
console.log(result);
});
}
},