How to properly mock data object for unit tests? - node.js

I'm trying to unit test a chunk of code that looks something like the snippet below.
const getSubscriptions = {
options: {
async handler({query}) {
const workType = searchParams.get('workType');
return axios.get(url, {
headers: {
Authorization: `Bearer ${clientCredentials.access_token}`
}
}).then((response) => {
if (workType && workType.length > 0) {
if (workType === 'Unknown') {
response.data._embedded = response.data._embedded.filter((subscriptions) => !subscriptions.systemProperties.workType);
} else {
response.data._embedded = response.data._embedded.filter((subscriptions) => subscriptions.systemProperties.workType && subscriptions.systemProperties.workType === workType);
}
}
return response.data;
})
}
},
method: 'GET',
path: '/subscriptions'};
I'm seeing errors such as "cannot read property 'filter' of undefined" in the existing tests after adding the .filter usage. How can I properly mock this in my unit tests so that filter is recognized as a function? Currently the tests are passing in a data object that looks like:
data: [chance.object(), chance.object()]
I've tried editing this data object to include _embedded and then I get an error that .filter is not a function.
I'm new to writing unit tests for JavaScript, so I've been stuck on this for a while. Any help is appreciated. I'm using Mocha for testing, but can likely interpret a Jasmine or Jest solution as well.
Here is a trimmed down version of the test suite:
describe('Get subscriptions', () => {
const workType = chance.word();
const getSubscriptionsResponse = {
data: [chance.object(), chance.object()]
};
const expectedRequest = {
auth: {
...expectedAuth
},
path: '/subscriptions',
query: {
workType
}
};
it('should call API with auth token for Subscriptions', async () => {
const axiosStub = sinon.stub(axios, 'get').resolves(getSubscriptionsResponse);
const expectedParams = new URLSearchParams(expectedRequest.query);
await getSubscriptionsRoute.options.handler(expectedRequest);
expect(axiosStub).to.have.callCount(1);
expect(axiosStub).to.be.calledWith(expectedUrl, {
headers: {
Authorization: `${accessToken}`
}
});
});

The code is trying to filter the response.data._embedded array — response.data is an object. You should mock the response data to be an object (with the _embedded property) and not an array.
{
data: [chance.object(), chance.object()]
}
should be
{
data: {
_embedded: [
// ...
]
}
}

Related

how to mock react-query useQuery in jest

I'm trying to mock out axios that is inside an async function that is being wrapped in useQuery:
import { useQuery, QueryKey } from 'react-query'
export const fetchWithAxios = async () => {
...
...
...
const response = await someAxiosCall()
...
return data
}
export const useFetchWithQuery = () => useQuery(key, fetchWithAxios, {
refetchInterval: false,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
retry: 1,
})
and I want to use moxios
moxios.stubRequest('/some-url', {
status: 200,
response: fakeInputData,
})
useFetchWithQuery()
moxios.wait(function () {
done()
})
but I'm getting all sorts of issues with missing context, store, etc which I'm iterested in mocking out completely.
Don't mock useQuery, mock Axios!
The pattern you should follow in order to test your usages of useQuery should look something like this:
const fetchWithAxios = (axios, ...parameters) => {
const data = axios.someAxiosCall(parameters);
return data;
}
export const useFetchWithQuery = (...parameters) => {
const axios = useAxios();
return useQuery(key, fetchWithAxios(axios, ...parameters), {
// options
})
}
Where does useAxios come from? You need to write a context to pass an axios instance through the application.
This will allow your tests to look something like this in the end:
const { result, waitFor, waitForNextUpdate } = renderHook(() => useFetchWithQuery(..., {
wrapper: makeWrapper(withQueryClient, withAxios(mockedAxios)),
});
await waitFor(() => expect(result.current.isFetching).toBeFalsy());

Implementing retry logic in node-fetch api call

I hope you will forgive the beginners question, I'm trying to implement a simple retry policy for an api call using node-fetch
This is being added to an existing repo using TypeScript so I'm using this also, hence the data definitions.
async checkStatus(custId: string, expectedStatus: string) {
const response = await fetch(
`${'env.API_URL'}/api/customer/applications/${custId}`,
{
method: 'GET',
headers: headers,
},
)
expect(response.status, "Response status should be 200").to.be.equal(200)
const resp = await response.json()
expect(resp.status).to.contain(expectedStatus)
return resp.status;
}
I am calling it like so
await this.checkApplicationStatus(custId, 'NEW')
await this.checkApplicationStatus(custId, 'EXISTING')//and so forth
Is there a neat way of retrying based on an unexpected expectedStatus ?
Again, I appreciate there may be many examples out there but as a beginner, I am struggling to see a good/best-practice approach so looking for someone to provide an example. I don't need to use Chai assertions, this was just my first attempt.
TIA
you can check a library that I've published #teneff/with-retry
to use it you'll have to define error classes like these:
class UnexpectedStatus extends Error {
constructor(readonly status: number) {
super("Unexpected status returned from API");
}
}
class ResourceNotFound extends Error {
constructor() {
super("Resource not found in API");
}
}
and throw respectively depending on the status code:
export const getCustomerApplications = async ( custId, headers ) => {
const result = await fetch(`https://example.com/api/customer/applications/${custId}`, {
method: "GET",
headers,
});
if (result.status === 404) {
throw new ResourceNotFound();
} else if (result.status > 200) {
throw new UnexpectedStatus(result.status);
}
return result;
};
and then just use the HOF from the library to wrap your function, with options (how many retries should be attempted and for which errors should it be retrying)
const retryWhenReourceNotFoundOrUnexpectedStatus = withRetry({
errors: [UnexpectedStatus, ResourceNotFound],
maxCalls: 3,
});
const getCustomerApplicationsWithRetry =
retryWhenReourceNotFoundOrUnexpectedStatus(getCustomerApplications);
const result = await getCustomerApplicationsWithRetry(1234, {
Authorization: "Bearer mockToken",
});
Check this working setup

Mock multiple api call inside one function using Moxios

I am writing a test case for my service class. I want to mock multiple calls inside one function as I am making two API calls from one function. I tried following but it is not working
it('should get store info', async done => {
const store: any = DealersAPIFixture.generateStoreInfo();
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: store
});
const nextRequest = moxios.requests.at(1);
nextRequest.respondWith({
status: 200,
response: DealersAPIFixture.generateLocation()
});
});
const params = {
dealerId: store.dealerId,
storeId: store.storeId,
uid: 'h0pw1p20'
};
return DealerServices.retrieveStoreInfo(params).then((data: IStore) => {
const expectedOutput = DealersFixture.generateStoreInfo(data);
expect(data).toMatchObject(expectedOutput);
});
});
const nextRequest is always undefined
it throw error TypeError: Cannot read property 'respondWith' of undefined
here is my service class
static async retrieveStoreInfo(
queryParam: IStoreQueryString
): Promise<IStore> {
const res = await request(getDealerStoreParams(queryParam));
try {
const locationResponse = await graphQlRequest({
query: locationQuery,
variables: { storeId: res.data.storeId }
});
res.data['inventoryLocationCode'] =
locationResponse.data?.location?.inventoryLocationCode;
} catch (e) {
res.data['inventoryLocationCode'] = 'N/A';
}
return res.data;
}
Late for the party, but I had to resolve this same problem just today.
My (not ideal) solution is to use moxios.stubRequest for each request except for the last one. This solution is based on the fact that moxios.stubRequest pushes requests to moxios.requests, so, you'll be able to analyze all requests after responding to the last call.
The code will look something like this (considering you have 3 requests to do):
moxios.stubRequest("get-dealer-store-params", {
status: 200,
response: {
name: "Audi",
location: "Berlin",
}
});
moxios.stubRequest("graph-ql-request", {
status: 204,
});
moxios.wait(() => {
const lastRequest = moxios.requests.mostRecent();
lastRequest.respondWith({
status: 200,
response: {
isEverythingWentFine: true,
},
});
// Here you can analyze any request you want
// Assert getDealerStoreParams's request
const dealerStoreParamsRequest = moxios.requests.first();
expect(dealerStoreParamsRequest.config.headers.Accept).toBe("application/x-www-form-urlencoded");
// Assert graphQlRequest
const graphQlRequest = moxios.requests.get("POST", "graph-ql-request");
...
// Assert last request
expect(lastRequest.config.url).toBe("status");
});

How to mock the api call in jest for saga test

I am writing tests to test my saga. Can anyone guide me how I can change the code below so that I can mock the api call? I don`t want to test real data.
import { call, put } from 'redux-saga/effects';
import { API_BUTTON_CLICK_SUCCESS, } from './actions/consts';
import { getDataFromAPI } from './api';
it('apiSideEffect - fetches data from API and dispatches a success action', () => {
const generator = apiSideEffect();
expect(generator.next().value)
.toEqual(call(getDataFromAPI));
expect(generator.next().value)
.toEqual(put({ type: API_BUTTON_CLICK_SUCCESS }));
expect(generator.next())
.toEqual({ done: true, value: undefined });
});
The getDataFromAPI()
import axios from "axios";
export const getDataFromAPI =(
method: string,
url: string,
path: string,
data?: any
) =>{
switch (method) {
case "create": {
return axios
.post(url + path, data, {
headers: {
Accept: "application/json",
"content-type": "application/json"
}
})
.catch(error => {
throw error.response;
});
}
I have tried to use
jest.mock('../../src/Utilities/api');
const { callApi } = require('../../src/Utilities/api');
callApi.mockImplementation( () => console.log("some api call"));
I am having the error
TypeError: Cannot read property 'mockImplementation' of undefined
at Object.<anonymous> (src/Payments/PaymentSagas.spec.ts:10:17)
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:188:7)
I usually do
import * as apis from '../../src/Utilities/api';
jest.spyOn(api, "callApi");
api.callApi.mockImplementation(/* your mock */);
easily exportable as a per-se function
export function spyUtil(obj, name, mockFunction = undefined) {
const spy = jest.spyOn(obj, name);
let mock;
if (mockFunction) {
mock = jest.fn(mockFunction);
obj[name].mockImplementation(mock);
}
return { spy, mock };
}
and consumable, in your test
spyUtil(apis, "callApi", jest.fn())

How to manipulate the data of a query via JSON?

I'm using a third-party API (so far so good, I get the result), however as I'm new I'm having difficulty manipulating these results for the frontend (EJS)** ...
What the system should do is:
The site is a graphics panel, where I use the third-party API (it blocks the origin if consumed by the frontend) and I populate the data received in JSON, using the graphics plugin called (CHARTJS) .. The problem is there, because the API should be consumed as soon as the page is accessed and the graphics populated after consumption, but for me to consume this JSON I need to pass some QS ... These QSs when accessing the page for the first graphic are already automatic and can be manipulated by the form that contains the same page, in order to be able to carry out other queries ..
I am totally, lost, I would love some help and explanation ..
CONTROLLERS/LOGIN/LOGIN.JS
module.exports.index = (_application, _request, _response) => {
_response.render('login/index');
}
module.exports.check_login = (_application, _request, _response) => {
const JSON_MODEL = new _application.app.models.jsonDAO('fatVenda.xsjs', '\'BSC\'', '201807', 'vendaabs,vendam2');
JSON_MODEL.request();
console.log(JSON_MODEL.jsonData)
const VENDA_MODEL = new _application.app.models.vendaDAO('', '', '', '');
_response.render('home/home');
}
ROUTES/LOGIN/LOGIN.JS
module.exports = (_application) => {
_application.get('/login', (_request, _response) => {
_application.app.controllers.login.login.index(_application, _request, _response);
});
_application.post('/check_login', (_request, _response) => {
_application.app.controllers.login.login.check_login(_application, _request, _response);
});
}
MODELS/JSONDAO.JS
const REQUEST = require('request');
class JsonRequestDAO {
constructor(_xjs, _shoppingID, _periodUntil, _kpi){
this.xjs = _xjs;
this.shoppingID = _shoppingID;
this.periodUntil = _periodUntil;
this.kpi = _kpi;
}
request(){
REQUEST.get({
uri: `URL${this.xjs}`,
json: true,
qs: {
Shop: this.shoppingID,
PeriodoAte: this.periodUntil,
Kpi: this.kpi
},
headers: {
Authorization: 'Basic KEY',
ApiKey: 'KEY',
'Cache-Control': 'no-cache',
'Content-Type': 'application/json',
}
}, (_error, _response) => {
(_response.statusCode == 200) ? _response.body : console.log(_error);
});
}
}
module.exports = () => { return JsonRequestDAO; }
MODELS/VENDADAO.JS
class VendaDAO {
constructor(_shoppingName, _yearMonth, _competence, _sale, _salePercentage, _saleSquareMeter, _salePercentageSquareMeter){
this.shoppingName = _shoppingName;
this.yearMonth = _yearMonth;
this.competence = _competence;
this.sale = _sale;
this.salePercentage = _salePercentage;
this.saleSquareMeter = _saleSquareMeter;
this.salePercentageSquareMeter = _salePercentageSquareMeter;
}
static get shoppingName() { return this.shoppingName; }
static get yearMonth() { return this.yearMonth; }
static get competence() { return this.competence; }
static get sale() { return this.sale; }
static get salePercentage() { return this.salePercentage; }
static get saleSquareMeter() { return this.saleSquareMeter; }
static get salePercentageSquareMeter() { return this.salePercentageSquareMeter; }
}
module.exports = () => {
return VendaDAO;
}

Resources