Using react hooks inside NextJS /pages/api [duplicate] - javascript

I need a graphql client lib to run on node.js for some testing and some data mashup - not in a production capacity. I'm using apollo everywhere else (react-apollo, apollo's graphql-server-express). My needs are pretty simple.
Is apollo-client a viable choice? I can find no examples or docs on using it on node - if you're aware of any, please share.
Or maybe I should/can use the reference graphql client on node?

Apollo Client should work just fine on Node. You only have to install cross-fetch.
Here is a complete TypeScript implementation of Apollo Client working on Node.js.
import { ApolloClient, gql, HttpLink, InMemoryCache } from "#apollo/client";
import { InsertJob } from "./graphql-types";
import fetch from "cross-fetch";
const client = new ApolloClient({
link: new HttpLink({ uri: process.env.PRODUCTION_GRAPHQL_URL, fetch }),
cache: new InMemoryCache(),
});
client.mutate<InsertJob.AddCompany, InsertJob.Variables>({
mutation: gql`mutation insertJob($companyName: String!) {
addCompany(input: { displayName: $companyName } ) {
id
}
}`,
variables: {
companyName: "aaa"
}
})
.then(result => console.log(result));

Newer Apollo version provide a simpler approach to perform this, as described in Apollo docs, check the section "Standalone". Basically one can simply use ApolloLink in order to perform a query or mutation.
Below is copy of the example code from the docs as of writing this, with node-fetch usage as config to createHttpLink. Check the docs for more details on how to use these tools.
import { execute, makePromise } from 'apollo-link';
import { createHttpLink } from 'apollo-link-http';
import gql from 'graphql-tag';
import fetch from 'node-fetch';
const uri = 'http://localhost:4000/graphql';
const link = createHttpLink({ uri, fetch });
const operation = {
query: gql`query { hello }`,
variables: {} //optional
operationName: {} //optional
context: {} //optional
extensions: {} //optional
};
// execute returns an Observable so it can be subscribed to
execute(link, operation).subscribe({
next: data => console.log(`received data: ${JSON.stringify(data, null, 2)}`),
error: error => console.log(`received error ${error}`),
complete: () => console.log(`complete`),
})
// For single execution operations, a Promise can be used
makePromise(execute(link, operation))
.then(data => console.log(`received data ${JSON.stringify(data, null, 2)}`))
.catch(error => console.log(`received error ${error}`))

If someone is looking for a JavaScript version:
require('dotenv').config();
const gql = require('graphql-tag');
const ApolloClient = require('apollo-boost').ApolloClient;
const fetch = require('cross-fetch/polyfill').fetch;
const createHttpLink = require('apollo-link-http').createHttpLink;
const InMemoryCache = require('apollo-cache-inmemory').InMemoryCache;
const client = new ApolloClient({
link: createHttpLink({
uri: process.env.API,
fetch: fetch
}),
cache: new InMemoryCache()
});
client.mutate({
mutation: gql`
mutation popJob {
popJob {
id
type
param
status
progress
creation_date
expiration_date
}
}
`,
}).then(job => {
console.log(job);
})

You can make apollo-client work, but it's not the best option for this use case.
Try graphql-request instead.
Minimal GraphQL client supporting Node and browsers for scripts or simple apps
Features per npmjs:
Most simple & lightweight GraphQL client
Promise-based API (works with async / await)
Typescript support
Isomorphic (works with Node / browsers)
example:
import { request, gql } from 'graphql-request'
const query = gql`
{
Movie(title: "Inception") {
releaseDate
actors {
name
}
}
}
`
request('https://api.graph.cool/simple/v1/movies', query).then((data) => console.log(data))
I have no affiliation with this package.

Here is simple node js implementation.
'graphiql' client is good enough for development activities.
1. run npm install
2. start server with "node server.js"
3. hit "http://localhost:8080/graphiql" for graphiql client
server.js
var graphql = require ('graphql').graphql
var express = require('express')
var graphQLHTTP = require('express-graphql')
var Schema = require('./schema')
// This is just an internal test
var query = 'query{starwar{name, gender,gender}}'
graphql(Schema, query).then( function(result) {
console.log(JSON.stringify(result,null," "));
});
var app = express()
.use('/', graphQLHTTP({ schema: Schema, pretty: true, graphiql: true }))
.listen(8080, function (err) {
console.log('GraphQL Server is now running on localhost:8080');
});
schema.js
//schema.js
var graphql = require ('graphql');
var http = require('http');
var StarWar = [
{
"name": "default",
"gender": "default",
"mass": "default"
}
];
var TodoType = new graphql.GraphQLObjectType({
name: 'starwar',
fields: function () {
return {
name: {
type: graphql.GraphQLString
},
gender: {
type: graphql.GraphQLString
},
mass: {
type: graphql.GraphQLString
}
}
}
});
var QueryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: function () {
return {
starwar: {
type: new graphql.GraphQLList(TodoType),
resolve: function () {
return new Promise(function (resolve, reject) {
var request = http.get({
hostname: 'swapi.co',
path: '/api/people/1/',
method: 'GET'
}, function(res){
res.setEncoding('utf8');
res.on('data', function(response){
StarWar = [JSON.parse(response)];
resolve(StarWar)
console.log('On response success:' , StarWar);
});
});
request.on('error', function(response){
console.log('On error' , response.message);
});
request.end();
});
}
}
}
}
});
module.exports = new graphql.GraphQLSchema({
query: QueryType
});

In response to #YakirNa 's comment:
I can't speak to the other needs I described, but I have done a fair amount of testing. I ended up doing all of my testing in-process.
Most testing ends up being resolver testing, which I do via a jig that invokes the graphql library's graphql function with a test query and then validates the response.
I also have an (almost) end-to-end test layer that works at the http-handling level of express. It creates a fake HTTP request and verifies the response in-process. This is all within the server process; nothing goes over the wire. I use this lightly, mostly for testing JWT authentication and other request-level behavior that's independent of the graphql request body.

I was running into your same question, because I wanted to create a middleware service to prepare data from graphQL to a final frontend application,
to have :
optimised data representation (and standard output data interface)
faster response time
assuming that graphQL server is provided by an external provider , so no ownership to data model, directly with GQL
So I didn't want to implement GraphQL Apolloclient directly in a frontend framework like React / Angular, Vuejs... but manage the queries via Nodejs at backend of a REST API.
So this is the class wrapper for Apolloclient I was able to assemble (using typescript):
import ApolloClient from "apollo-client";
import { ApolloLink } from 'apollo-link'
import { HttpLink } from 'apollo-link-http'
import { onError } from 'apollo-link-error'
import fetch from 'node-fetch'
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'
import introspectionQueryResultData from '../../fragmentTypes.json';
import { AppConfig } from 'app-config';
const config: AppConfig = require('../../../appConfig.js');
export class GraphQLQueryClient {
protected apolloClient: any;
constructor(headers: { [name: string]: string }) {
const api: any = {
spaceId: config.app.spaceId,
environmentId: config.app.environmentId,
uri: config.app.uri,
cdnApiPreviewToken: config.cdnApiPreviewToken,
};
// console.log(JSON.stringify(api));
const ACCESS_TOKEN = api.cdnApiPreviewToken;
const uri = api.uri;
console.log(`Apollo client setup to query uri: ${uri}`);
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData
});
this.apolloClient = new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }:any) => {
if (graphQLErrors) {
graphQLErrors.map((el:any) =>
console.warn(
el.message || el
)
)
graphQLErrors.map(({ message, locations, path }:any) =>
console.warn(
`[GraphQL error - Env ${api.environmentId}]: Message: ${message}, Location: ${JSON.stringify(locations)}, Path: ${path}`
)
)
}
if (networkError) console.log(`[Network error]: ${networkError}`)
}),
new HttpLink({
uri,
credentials: 'same-origin',
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`
},
fetch
})
]),
cache: new InMemoryCache({ fragmentMatcher }),
// fetchPolicy as network-only avoids using the cache.
defaultOptions: {
watchQuery: {
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
},
query: {
fetchPolicy: 'network-only',
errorPolicy: 'all',
},
}
});
}
}
After this constructor I run queries like :
let response = await this.apolloClient.query({ query: gql`${query}` });
As you might have noticed:
I needed to inject fetch on Httplink
I had to setup Authorization headers to access external provider graphQL endpoint
I used IntrospectionFragmentMatcher in order to use Fragments in my queries, along with building schema type ("fragmentTypes.json" with an init script)
Posting this to just add my experience and maybe more info for the question.
Also looking forward for comments and points of improvement for this wrapper.

Related

Variable "$file" got invalid value {}; Upload value invalid

I am using GraphQLClient from graphql-request to send requests to my server. I am trying to upload a file by doing the following:
const graphQLClient = new GraphQLClient('http://localhost:4000/graphql', {
credentials: 'include',
mode: 'cors',
});
const source = gql`
mutation uploadImage($file: Upload!) {
uploadImage(file: $file)
}
`;
const file: RcFile = SOME_FILE; // RcFile (from antd) extends File
await graphQLClient.request<{uploadImage: boolean}>(source, { file });
However, when I send a request to my server this way I get the following error:
GraphQLError: Variable \"$file\" got invalid value {}; Upload value invalid
This is what my request looks like in the console:
operations: {
"query":"\n mutation uploadProfileImage($file: Upload!){\n uploadProfileImage(file: $file)\n }\n",
"variables":{"file":null}
}
map: {"1":["variables.file"]}
1: (binary)
Has anyone else had this issue? I can't seem to upload a file to my backend.
I fixed the issue by setting the uploads option to false in the ApolloServer configuration.
new ApolloServer({ schema, context, uploads: false })
And then using the graphqlUploadExpress() middleware from graphql-upload.
app.use(graphqlUploadExpress({ maxFileSize: 10000, maxFiles: 10 }));
Hopefully this helps anyone who runs into the same issue I did 😊
it depends on ApolloClient that you used.
1- If used import { ApolloClient } from 'apollo-client' must be used "createUploadLink" instead of "createHttpLink "means,
import { createUploadLink } from 'apollo-upload-client'
const httpLink = createUploadLink({
uri: httpEndpoint,
})
2- if used createApolloClient, exact this package:
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client'
const { apolloClient, wsClient } = createApolloClient({
...defaultOptions,
...options,
})
``
You do not need to set anything and Upload work complete.
In addition to the ApolloServer implementation described in the accepted answer (and to clarify #Masoud 's answer), make sure you also have the following client implementation using apollo-upload-client:
import { ApolloClient, InMemoryCache } from "#apollo/client";
import { createUploadLink } from 'apollo-upload-client';
const client = new ApolloClient({
cache: new InMemoryCache(),
link: createUploadLink({
uri: 'http://localhost:4000/graphql'
}),
});

Cloudinary Signed Uploads with Widget

Documentation is extremely frustrating.
I'm using the upload widget to try to allow users to upload multiple pictures for their profile. I can't use unsigned uploads because of the potential for abuse.
I would much rather upload the file through the upload widget instead of through the server as it seems like it should be so simple
I've pieced together what I think should work but it is still saying: Upload preset must be whitelisted for unsigned uploads
Server:
// grab a current UNIX timestamp
const millisecondsToSeconds = 1000;
const timestamp = Math.round(Date.now() / millisecondsToSeconds);
// generate the signature using the current timestmap and any other desired Cloudinary params
const signature = cloudinaryV2.utils.api_sign_request({ timestamp }, CLOUDINARY_SECRET_KEY);
// craft a signature payload to send to the client (timestamp and signature required)
return signature;
also tried
return {
signature,
timestamp,
};
also tried
const signature = cloudinaryV2.utils.api_sign_request(
data.params_to_sign,
CLOUDINARY_SECRET_KEY,
);
Client:
const generateSignature = async (callback: Function, params_to_sign: object): Promise<void> => {
try {
const signature = await generateSignatureCF({ slug: 'xxxx' });
// also tried { slug: 'xxxx', params_to_sign }
callback(signature);
} catch (err) {
console.log(err);
}
};
cloudinary.openUploadWidget(
{
cloudName: 'xxx',
uploadPreset: 'xxxx',
sources: ['local', 'url', 'facebook', 'dropbox', 'google_photos'],
folder: 'xxxx',
apiKey: ENV.CLOUDINARY_PUBLIC_KEY,
uploadSignature: generateSignature,
},
function(error, result) {
console.log(error);
},
);
Let's all take a moment to point out how horrible Cloudinary's documentation is. It's easily the worst i've ever seen. Nightmare fuel.
Now that i've got that off my chest... I really needed to be able to do this and I spent way too long banging my head against walls for what should be extremely simple. Here it is...
Server (Node.js)
You'll need an endpoint that returns a signature-timestamp pair to the frontend:
import cloudinary from 'cloudinary'
export async function createImageUpload() {
const timestamp = new Date().getTime()
const signature = await cloudinary.utils.api_sign_request(
{
timestamp,
},
process.env.CLOUDINARY_SECRET
)
return { timestamp, signature }
}
Client (Browser)
The client makes a request to the server for a signature-timestamp pair and then uses that to upload a file. The file used in the example should come from an <input type='file'/> change event etc.
const CLOUD_NAME = process.env.CLOUDINARY_CLOUD_NAME
const API_KEY = process.env.CLOUDINARY_API_KEY
async function uploadImage(file) {
const { signature, timestamp } = await api.post('/image-upload')
const form = new FormData()
form.append('file', file)
const res = await fetch(
`https://api.cloudinary.com/v1_1/${CLOUD_NAME}/image/upload?api_key=${API_KEY}&timestamp=${timestamp}&signature=${signature}`,
{
method: 'POST',
body: form,
}
)
const data = await res.json()
return data.secure_url
}
That's it. That's all it takes. If only Cloudinary had this in their docs.
Man. I hate my life. I finally figured it out. It literally took me beautifying the upload widget js to understand that the return of the function should be a string instead of an object even though the docs make it seem otherwise.
Here is how to implement a signed upload with a Firebase Cloud Function
import * as functions from 'firebase-functions';
import cloudinary from 'cloudinary';
const CLOUDINARY_SECRET_KEY = functions.config().cloudinary.key;
const cloudinaryV2 = cloudinary.v2;
module.exports.main = functions.https.onCall(async (data, context: CallableContext) => {
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.',
);
}
try {
return cloudinaryV2.utils.api_sign_request(data.params_to_sign, CLOUDINARY_SECRET_KEY);
} catch (error) {
throw new functions.https.HttpsError('failed-precondition', error.message);
}
});
// CLIENT
const uploadWidget = () => {
const generateSignature = async (callback: Function, params_to_sign: object): Promise<void> => {
try {
const signature = await generateImageUploadSignatureCF({ params_to_sign });
callback(signature.data);
} catch (err) {
console.log(err);
}
};
cloudinary.openUploadWidget(
{
cloudName: 'xxxxxx',
uploadSignature: generateSignature,
apiKey: ENV.CLOUDINARY_PUBLIC_KEY,
},
function(error, result) {
console.log(error);
},
);
};

How to send one-off query with Relay?

I'm trying to send a one-off query using Relay. So, I don't necessarily want to use QueryRenderer but rather have a simple API to send a query without binding the result to a React component.
Previously, this was possible with the fetch method on network:
const checkVoteQueryText = `
query CheckVoteQuery($userId: ID!, $linkId: ID!) {
viewer {
allVotes(filter: {
user: { id: $userId },
link: { id: $linkId }
}) {
edges {
node {
id
}
}
}
}
}`
const checkVoteQuery = { text: checkVoteQueryText }
const result = await this.props.relay.environment._network.fetch(checkVoteQuery, {userId, linkId})
It seems however that fetch has been deprecated in some newer version of Relay. Is there an alternative that can be used now?
If Relay doesn't allow for one-off queries, I'd probably just graphql-request or plain JS fetch to get the job done. But it would be nice to be able to use Relay for this as it already knows my endpoint.
Ah this was actually rather simple to achieve. In fact, calling fetch on this.props.relay.environment._network only reused the function that's passed into the Network when it's created. So, it's possible to just reuse this function, in my case it's called fetchQuery:
Environment.js
import {GC_AUTH_TOKEN} from './constants'
import { SubscriptionClient } from 'subscriptions-transport-ws'
const {
Environment,
Network,
RecordSource,
Store,
} = require('relay-runtime')
const store = new Store(new RecordSource())
// Export it here so it can be reused in other files
export const fetchQuery = (operation, variables) => {
return fetch('https://api.graph.cool/relay/v1/cj9h5g99s24fb0100nsp81d4y', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem(GC_AUTH_TOKEN)}`
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json()
})
}
const network = Network.create(fetchQuery)
const environment = new Environment({
network,
store,
})
export default environm
ent

Wrapping a RESTapi with GraphQL using Apollo React

I need to do a project (currency exchange app) using Apollo client and React. I need to wrap an existing REST api (fixer.io) with graphql. So far no luck finding a solution online. Tried several tutorials but they don't seem to work. Anyone have experience with this?
Thanks.
I assume you use Apollo client 2.0 and want everything to be client side.
First you need an apollo bridge link. It's used "When you don't have GraphQL server (yet) and want to use GraphQL on the client". Its source code is quite short, so you can inline it:
/*
Copyright (c) 2017 David Cizek
https://github.com/dacz/apollo-bridge-link
*/
import { GraphQLSchema, graphql, print } from 'graphql';
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';
import { ApolloLink } from 'apollo-link';
import Observable from 'zen-observable';
export const createBridgeLink = ({ schema, resolvers, mock, context = {} }) => {
let executableSchema;
if (typeof schema === 'string') {
executableSchema = makeExecutableSchema({ typeDefs: schema, resolvers });
} else if (schema.kind === 'Document') {
executableSchema = makeExecutableSchema({
typeDefs: print(schema),
resolvers,
});
} else if (schema instanceof GraphQLSchema) {
executableSchema = schema;
} else {
throw new Error('schema should be plain text, parsed schema or executable schema.');
}
if (mock)
{addMockFunctionsToSchema({
schema: executableSchema,
preserveResolvers: true,
});}
return new ApolloLink(
operation =>
new Observable(observer => {
const { headers, credentials } = operation.getContext();
const ctx = {
...context,
headers,
credentials,
};
graphql(executableSchema, print(operation.query), undefined, ctx, operation.variables, operation.operationName)
.then(data => {
observer.next(data);
observer.complete();
})
.catch(err => {
/* istanbul ignore next */
observer.error(err);
});
}),
);
};
export class BridgeLink extends ApolloLink {
requester;
constructor(opts) {
super();
this.requester = createBridgeLink(opts).request;
}
request(op) {
return this.requester(op);
}
}
Next you create schema and resolvers:
// schema.js
export default `
type Rate {
date: Date!
rate: Float!
}
type Query {
latestRate(from: String!, to: String!): Rate
}
schema {
query: Query
}
`;
// resolvers.js
const resolvers = {
Query: {
latestRate(obj, args, context, info) {
return fetch(`https://api.fixer.io/latest?base=${args.from}`).then(res => res.json())
.then(res => { date: res.date, rate: res.rates[args.to] })
}
}
}
export default resolvers;
Finally, you create an apollo client factory:
// clientFactory.js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { BridgeLink } from './apollo-bridge-link';
import schema from './schema';
import resolvers from './resolvers';
export default () => {
const mock = false;
const context = {};
const client = new ApolloClient({
link: new BridgeLink({ schema, resolvers, mock, context }),
cache: new InMemoryCache(),
});
return client;
};
Here's how you use it:
import gql from 'graphql-tag';
import clientFactory from './clientFactory'
const client = clientFactory();
client.query(gql`query {
latestRate(from: "USD", to: "EUR") { date, rate }
}`).then(console.log)
If you want to use it in React:
import { ApolloProvider } from 'react-apollo';
const client = clientFactory();
const App = ({ data: { latestRate, refetch } }) => {
return <div>
<span>Today:</span><span>{latestRate.date}</span>
<span>1 USD equals:</span><span>{latestRate.rate} EUR</span>
<button onClick={() => refetch()}>
Refresh
</button>
</div>
}
const AppWithQuery = graphql(gql`
query {
latestRate(from: "USD", to: "EUR") { date, rate }
}
`)(App);
ReactDOM.render(
<ApolloProvider client={client}>
<AppWithQuery/>
</ApolloProvider>,
document.getElementById('root'),
);
With the Graphcool Framework, you can define resolver functions, which allow you to easily wrap any REST API. You can define a function and connect it to a specific mutation or query in your GraphQL Schema.
I prepared a demo, wrapping the fixer API.
Try to run this query to get the exchange rates with USD as base, for example:
query {
fixer(
base: "USD"
) {
base
date
eur
usd
rub
}
}
You can build this demo yourself like this:
git clone git#github.com:graphcool/templates.git
cd templates/curated/misc/fixer-wrapper
npm install -g graphcool#next
graphcool init
graphcool deploy
graphcool playground
Please feel free to share any improvement you might have in mind, the example is open source. You can read more about resolvers here.

How do I test axios in Jest?

I have this action in React:
export function fetchPosts() {
const request = axios.get(`${WORDPRESS_URL}`);
return {
type: FETCH_POSTS,
payload: request
}
}
How do I test Axios in this case?
Jest has this use case on their site for asynchronous code where they use a mock function, but can I do this with Axios?
Reference: An Async Example
I have done this so far to test that it is returning the correct type:
it('should dispatch actions with the correct type', () => {
store.dispatch(fetchPosts());
let action = store.getActions();
expect(action[0].type).toBe(FETCH_POSTS);
});
How can I pass in mock data and test that it returns?
Without using any other libraries:
import * as axios from "axios";
// Mock out all top level functions, such as get, put, delete and post:
jest.mock("axios");
// ...
test("good response", () => {
axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
// ...
});
test("bad response", () => {
axios.get.mockImplementation(() => Promise.reject({ ... }));
// ...
});
It is possible to specify the response code:
axios.get.mockImplementation(() => Promise.resolve({ status: 200, data: {...} }));
It is possible to change the mock based on the parameters:
axios.get.mockImplementation((url) => {
if (url === 'www.example.com') {
return Promise.resolve({ data: {...} });
} else {
//...
}
});
Jest v23 introduced some syntactic sugar for mocking Promises:
axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
It can be simplified to
axios.get.mockResolvedValue({ data: {...} });
There is also an equivalent for rejected promises: mockRejectedValue.
Further Reading:
Jest mocking documentation
A GitHub discussion that explains about the scope of the jest.mock("axios") line.
A related question which addresses applying the techniques above to Axios request interceptors.
Using jest functions like mockImplementation in TypeScript: Typescript and Jest: Avoiding type errors on mocked functions
I used axios-mock-adapter.
In this case the service is described in ./chatbot.
In the mock adapter you specify what to return when the API endpoint is consumed.
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import chatbot from './chatbot';
describe('Chatbot', () => {
it('returns data when sendMessage is called', done => {
var mock = new MockAdapter(axios);
const data = { response: true };
mock.onGet('https://us-central1-hutoma-backend.cloudfunctions.net/chat').reply(200, data);
chatbot.sendMessage(0, 'any').then(response => {
expect(response).toEqual(data);
done();
});
});
});
You can see it the whole example here:
Service:
https://github.com/lnolazco/hutoma-test/blob/master/src/services/chatbot.js
Test:
https://github.com/lnolazco/hutoma-test/blob/master/src/services/chatbot.test.js
I could do that following the steps:
Create a folder __mocks__/ (as pointed by #Januartha comment)
Implement an axios.js mock file
Use my implemented module on test
The mock will happen automatically
Example of the mock module:
module.exports = {
get: jest.fn((url) => {
if (url === '/something') {
return Promise.resolve({
data: 'data'
});
}
}),
post: jest.fn((url) => {
if (url === '/something') {
return Promise.resolve({
data: 'data'
});
}
if (url === '/something2') {
return Promise.resolve({
data: 'data2'
});
}
}),
create: jest.fn(function () {
return this;
})
};
Look at this
The function to test album.js
const fetchAlbum = function () {
return axios
.get("https://jsonplaceholder.typicode.com/albums/2")
.then((response) => {
return response.data;
});
};
The test album.test.js
const axios = require("axios");
const { fetchAlbum } = require("../utils.js");
jest.mock("axios");
test("mock axios get function", async () => {
expect.assertions(1);
const album = {
userId: 1,
id: 2,
title: "sunt qui excepturi placeat culpa",
};
const payload = { data: album };
// Now mock axios get method
axios.get = jest.fn().mockResolvedValue(payload);
await expect(fetchAlbum()).resolves.toEqual(album);
});
I've done this with nock, like so:
import nock from 'nock'
import axios from 'axios'
import httpAdapter from 'axios/lib/adapters/http'
axios.defaults.adapter = httpAdapter
describe('foo', () => {
it('bar', () => {
nock('https://example.com:443')
.get('/example')
.reply(200, 'some payload')
// test...
})
})
For those looking to use axios-mock-adapter in place of the mockfetch example in the Redux documentation for async testing, I successfully used the following:
File actions.test.js:
describe('SignInUser', () => {
var history = {
push: function(str) {
expect(str).toEqual('/feed');
}
}
it('Dispatches authorization', () => {
let mock = new MockAdapter(axios);
mock.onPost(`${ROOT_URL}/auth/signin`, {
email: 'test#test.com',
password: 'test'
}).reply(200, {token: 'testToken' });
const expectedActions = [ { type: types.AUTH_USER } ];
const store = mockStore({ auth: [] });
return store.dispatch(actions.signInUser({
email: 'test#test.com',
password: 'test',
}, history)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
In order to test a successful case for signInUser in file actions/index.js:
export const signInUser = ({ email, password }, history) => async dispatch => {
const res = await axios.post(`${ROOT_URL}/auth/signin`, { email, password })
.catch(({ response: { data } }) => {
...
});
if (res) {
dispatch({ type: AUTH_USER }); // Test verified this
localStorage.setItem('token', res.data.token); // Test mocked this
history.push('/feed'); // Test mocked this
}
}
Given that this is being done with jest, the localstorage call had to be mocked. This was in file src/setupTests.js:
const localStorageMock = {
removeItem: jest.fn(),
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
New tools for testing have been introduced since the question was initially answered.
The problem with mocking is that you often test the mock and not the real context of your code, leaving some areas of this context untested.
An improvement over telling axios what promise to return is intercepting http requests via Service Workers.
Service worker is a client-side programmable proxy between your web app and the outside world. So instead of mocking promise resolution it is a more broader solution to mock the proxy server itself, intercepting requests to be tested. Since the interception happens on the network level, your application knows nothing about the mocking.
You can use msw (Mock Service Worker) library to do just that. Here is a short video explaining how it works.
The most basic setup I can think of is this:
1️⃣ set up handlers, which are similar to express.js routing methods;
2️⃣ set up mock server and pass handlers as it’s arguments;
3️⃣ configure tests to so that mock server will intercept our requests;
4️⃣ perform tests;
5️⃣ close mock server.
Say you want to test the following feature:
import axios from "axios";
export const fetchPosts = async () => {
const request = await axios.get("/some/endpoint/");
return {
payload: request,
};
};
Then test could look like this:
import { rest } from "msw";
import { setupServer } from "msw/node";
import fetchPosts from "./somewhere";
// handlers are usually saved in separate file(s) in one destined place of the app,
// so that you don't have to search for them when the endpoints have changed
const handlers = [ 1️⃣
rest.get("/some/endpoint/", (req, res, ctx) =>
res(ctx.json({ message: "success" }))
),
];
const server = setupServer(...handlers); 2️⃣
beforeAll(() => {
server.listen(); 3️⃣
});
describe("fetchPosts", () => {
it("should return 'success' message", async () => {
const resp = await fetchPosts();
expect(resp.payload?.data?.message).toEqual("success"); 4️⃣
});
});
afterAll(() => {
server.close(); 5️⃣
});
The configuration may be different depending on framework you are using. Some general examples for, among others, React (both REST and GraphQL) and Angular can be found on MSW’ repo. A Vue example is provided by VueMastery.
You can also find examples on MSW' recipes page.

Categories

Resources