Axios Mock Adapter with repeated params - reactjs

I am am using a mock adapter for my tests in a react app, and one of them looks like this:
http://endpoint.com/api/entities?id=123&select=labels&select=versions&labelsLang=en
The important part to note is that the select parameter is in there twice.
One of the tests is rendering in another language, so we have two mock endpoints set up to reflect this, however I cannot seem to find a way to properly mock the repeated paramter. I just keep on getting a result for the first one.
The code for the mocked endpoints is this:
const mockApiClient = axios.create({ baseURL: "http://localhost" });
const mock = new MockAdapter(mockApiClient);
const params1 = new URLSearchParams();
params1.append("id", "123");
params1.append("select", "labels");
params1.append("select", "versions");
params1.set("labelsLang", "en");
mock
.onGet("/entities", {
asymmetricMatch: function(actual: any) {
return actual.params.toString() === params1.toString();
},
})
.reply(200, getCompanyResponse);
const params2 = new URLSearchParams();
params2.append("id", "123");
params2.append("select", "labels");
params2.append("select", "versions");
params2.set("labelsLang", "de");
mock
.onGet("/entities", {
asymmetricMatch: function(actual: any) {
return actual.params.toString() === params2.toString();
},
})
.reply(200, getCompanyResponseDE);
I know it's messy, I just want to understand how to do this properly.
Whenever I try specifying specific params in an object, it complains that you cant have a duplicate key .
(ie.
{ params:{select:'labels', select:'versions} })

Solved.
Here's how it was done:
const mockApiClient = axios.create({ baseURL: "http://localhost" });
const mock = new MockAdapter(mockApiClient);
const params1 = {
"id": "123",
select: ["labels", "versions"],
"labelsLang": "en",
};
mock
.onGet("/entities", {
params: {
asymmetricMatch: function(actual: any) {
actual.sort(); // Important, as without it the order of params would affect the result
return actual.toString() === toURLSearchParams(params1).toString();
},
},
})
.reply(200, getCompanyResponse);
export const toURLSearchParams = (params: {[key: string]: string|string[]}, sort:boolean = true):URLSearchParams => {
if(params instanceof URLSearchParams) return params
const searchParams = new URLSearchParams();
for(const key in params){
const value = params[key];
if(Array.isArray(value)){
value.forEach((eachValue) => {
searchParams.append(key, eachValue)
})
} else {
searchParams.append(key,value)
}
}
if(sort) searchParams.sort() // Likewise here.
return searchParams;
}

Related

How to generate the list of route segment parameters for every pokemon using generateStaticParams in Next.js 13?

I am trying to use Nextjs 13's generateStaticParams to render static data into my web page. How do I generate the list of routes like /pokemon/[pokemonName] for every pokemon name using the generateStaticParams without providing hard-coded pokemon name (for example here I put "charmander" as argument for fetchData and then it generate route just for charmander)?
export const generateStaticParams = async (): Promise<PageParams[]> => {
const res = await fetchData("charmander");
return [
{
pokemonName: res?.data.pokemon.name,
},
];
};
Fetching pokemon moves from graphQL-pokeAPI:
const fetchData = async (pokemonName: string) => {
const POKEMON_MOVES = `
query pokemon($name: String!) {
pokemon(name: $name) {
id
name
sprites {
front_default
}
moves {
move {
name
}
}
}
}
`;
const res = await fetch("https://graphql-pokeapi.graphcdn.app/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: POKEMON_MOVES,
variables: { name: pokemonName },
}),
});
const moves = await res.json();
return moves;
};
export default function SpecificPokemon({ params }) {
const { pokemonName } = params;
const pokemonData = use(fetchData(pokemonName));
return (<h1>{pokemonName}</h1>...)
};
In the Nextjs13 beta docs, it said that generateStaticParams doesn't require any context parameters. So I can't pass pokemonName into generateStaticParams like this, right?
export const generateStaticParams = async (pokemonName: string) => {
const res = await fetchData(pokemonName);
I tried to just write fetchData("") and the page just blank. Also, it would be too many to write like this:
return [
{ pokemonName: "bulbasaur" },
{ pokemonName: "ivysaur" },
{ pokemonName: "venusaur" },
];
Also, is this due to my lack of JS / Next13 concept understanding?
They actually have a feature called getStaticPaths which was specifically designed for this. You can easily make a call to a pokemon api and parse the response into getStaticPaths at run to generate the static paths you want to use. I encourage you to play with the documentation more, that team did a killer job with them.
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
})
);
}
Here's an example for a posts table - but basically you get all the posts or in your case Pokemon, and map the names to an array.
I'm not sure if there's a better way of handling it but that should point you in the right direction. I'm still reading up on NextJS and especially the new Beta myself, so take with a pinch of salt.

How to mock Axios service wrapper using JEST

I'm using JEST testing framework to write test cases for my React JS application. I'm using our internal axios wrapper to make service call. I would like to mock that wrapper service using JEST. Can someone help me on this ?
import Client from 'service-library/dist/client';
import urls from './urls';
import { NODE_ENV, API_VERSION } from '../screens/constants';
const versionHeader = 'X-API-VERSION';
class ViewServiceClass extends Client {
getFiltersList(params) {
const config = {
method: urls.View.getFilters.requestType,
url: urls.View.getFilters.path(),
params,
headers: { [versionHeader]: API_VERSION },
};
return this.client.request(config);
}
const options = { environment: NODE_ENV };
const ViewService = new ViewServiceClass(options);
export default ViewService;
Above is the Service Implementation to make API call. Which I'm leveraging that axios implementation from our internal library.
getFiltersData = () => {
const params = {
filters: 'x,y,z',
};
let {
abc,
def,
ghi
} = this.state;
trackPromise(
ViewService.getFiltersList(params)
.then((result) => {
if (result.status === 200 && result.data) {
const filtersJson = result.data;
.catch(() =>
this.setState({
alertMessage: 'No Filters Data Found. Please try after some time',
severity: 'error',
showAlert: true,
})
)
);
};
I'm using the ViewService to get the response, and I would like to mock this service. Can someone help me on this ?
You would need to spy your getFiltersList method from ViewServiceClass class.
Then mocking some response data (a Promise), something like:
import ViewService from '..';
const mockedData = {
status: 'ok',
data: ['some-data']
};
const mockedFn = jest.fn(() => Promise.resolve(mockedData));
let getFiltersListSpy;
// spy the method and set the mocked data before all tests execution
beforeAll(() => {
getFiltersListSpy = jest.spyOn(ViewService, 'getFiltersList');
getFiltersListSpy.mockReturnValue(mockedFn);
});
// clear the mock the method after all tests execution
afterAll(() => {
getFiltersListSpy.mockClear();
});
// call your method, should be returning same content as `mockedData` const
test('init', async () => {
const response = await ViewService.getFiltersList();
expect(response).toEqual(mockedData);
});
P.D: You can pass params to the method, but you will need to configure as well the mockedData as you wish.

RelayObservable: Unhandled Error TypeError: Cannot read property 'subscribe' of undefined in React and Relay

I have followed the subscription tutorial on How to GraphQL React + Relay (https://relay.dev/docs/en/subscriptions) but still not working.
I'm using Relay Modern in my app and have successfully integrated query but not working the requestSubscription function.
Any help would be awesome.
My environment.js file:
function setupSubscription(
config,
variables,
cacheConfig,
observer,
) {
const query = config.text
const subscriptionClient = new SubscriptionClient('ws://192.168.1.19:8000/subscriptions', {reconnect: true});
const id = subscriptionClient.on({query, variables}, (error, result) => {
console.log(result,'result');
observer.onNext({data: result})
})
}
const network = Network.create(fetchQuery, setupSubscription)
const environment = new Environment({
network,
store
});
export default environment;
- My Subscription.js file:
const subscription = graphql`
subscription newVoteSubscription {
leaderboardUpdate {
id,
game_id,
event_id,
colarr,
rowarr
}
}
`;
function newVoteSubscription(callback) {
const variables = {};
return requestSubscription(environment, {
subscription: subscription,
variables: variables,
onError: (error)=> {
console.log(error, "error");
},
onNext: (res) => {
console.log(res,'onNext');
// callback();
},
updater: proxyStore => {
console.log(proxyStore,'proxyStore');
},
onCompleted: () => {
console.log('test');
},
});
}
export default newVoteSubscription;
I had trouble with the network as well. On Relay 7 using an Observable worked for me. This also handles error cases and the server closing the subscription.
const subscriptionClient = new SubscriptionClient('ws://192.168.1.19:8000/subscriptions', {reconnect: true})
function setupSubscription(
request,
variables,
cacheConfig,
) {
const query = request.text;
// Observable is imported from the relay-runtime package
return Observable.create(sink => {
const c = subscriptionClient.request({ query, variables }).subscribe(sink);
return c.unsubscribe;
});
}
I'm not sure why i've gone with the sink / unsubscribe approach, but this is what worked for me. As far as i remember the observable types used by relay and subscriptions-transport-ws were not compatible.
Also i'd advise you to hoist the new SubscriptionClient() call outside of the setupSubscription function as otherwise you'll open a new WebSocket for each subscription request.
I got the response, but now observer.onNext is undefined.
My updated code environment.js:
const setupSubscription = (config, variables, cacheConfig, observer) => {
const query = config.text
const subscriptionClient = new SubscriptionClient('ws://192.168.1.19:8000/subscriptions', {reconnect: true})
subscriptionClient.request({ query, variables }).subscribe((result) => {
observer.onNext({data: result})
});
return Observable.create(() => {
return subscriptionClient;
});
}
const environment = new Environment({
network: Network.create(fetchQuery, setupSubscription),
store: new Store(new RecordSource())
});

changing process.env variables with jest in tests

I'm trying to test a function, which has multiple if statements with variables from process.env.
I was trying to write a test in jest like this:
beforeEach(() => {
delete process.env.REACT_APP_API_KEY;
delete process.env.REACT_APP_URL;
});
it('no URL', () => {
process.env.REACT_APP_API_KEY = 'api_key';
try {
buildUrl(mockMethod, null);
} catch (err) {
expect(err.message).toBe('REACT_APP_API_KEY is not specified!');
}
});
it('no method', () => {
process.env.REACT_APP_URL = 'mock_url';
process.env.REACT_APP_API_KEY = 'api_key';
try {
buildUrl(mockMethod, null);
} catch (err) {
expect(err.message).toBe('REACT_APP_API_KEY is not specified!');
}
});
But the problem is that the variables don't get deleted after every test. They get cached somehow and not set anymore.
Maybe someone has encountered this problem and has some tips on how to solve it.
I managed to solve my problem, the culprit was that I was deconstructing process.env values in the upper part of the file - not in the function as itself + I was renaming them.
const buildUrl = (method: string, page: number = 1): string => {
const { API_KEY, URL } = process.env;
//...
// vs
const {
API_KEY: MY_API_KEY,
URL: MY_URL
} = process.env;
const buildUrl = (method: string, page: number = 1): string => {
const MY_API_KEY = ...

Mocking a HttpClient with Jest

I'm having trouble with my React Native + Jest + Typescript setup.
I'm trying to test a thunk/network operation. I've created a networkClient function:
export const networkClient = async (
apiPath: string,
method = RequestType.GET,
body = {},
authenticate = true,
appState: IAppState,
dispatch: Dispatch<any>
) => {
... validate/renew token, validate request and stuff...
const queryParams = {
method,
headers: authenticate
? helpers.getHeadersWithAuth(tokenToUse)
: helpers.getBaseHeaders(),
body: method === RequestType.POST ? body : undefined,
};
const fullUri = baseURL + apiPath;
const result = await fetch(fullUri, queryParams);
if (result.ok) {
const json = await result.json();
console.log(`Result ${result.status} for request to ${fullUri}`);
return json;
} else {
... handle error codes
}
} catch (error) {
handleNetworkError(error, apiPath);
}
};
Now, when writing my tests for the operations which uses the networkClient above to request server data like so:
const uri = `/subscriptions/media` + tokenParam;
const json = await networkClient(
uri,
RequestType.GET,
undefined,
true,
getState(),
dispatch
);
I'd like to mock the implementation to return a mock response pr. test.
As pr the docs, I thought this could be done like so:
import { RequestType, networkClient} from './path/to/NetworkClient';
and in the test:
networkClient = jest.fn(
(
apiPath: string,
method = RequestType.GET,
body = {},
authenticate = true,
appState: IAppState,
dispatch: Dispatch<any>
) => {
return 'my test json';
}
);
const store = mockStore(initialState);
return store
.dispatch(operations.default.getMoreFeedData(false))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(store.getState().feedData).toEqual(testFeed);
// fetchMock.restore();
});
but networkClient is not defined, and ts tells me
[ts] Cannot assign to 'networkClient' because it is not a variable.
What did I get wrong? I must have missed something about how Jest mocks modules and how to provide a mock implementation somewhere, but I can't find it on neither the docs, nor on Google/SO.
Any help is much appreciated
So I found the solution
The import should not be
import { RequestType, networkClient} from './path/to/NetworkClient';
but instead the module should be required like so:
const network = require('./../../../../networking/NetworkClient');
After that, i could successfully mock the implementation and complete the test:
const testFeed = { items: feed, token: 'nextpage' };
network.networkClient = jest.fn(() => {
return testFeed;
});
I hope it helps someone

Resources