How to test GraphQL queries with fragments using jest - graphql

Problem: I would like to test a GraphQL query that lives in a .graphql file like this:
#import '../../fragments/Widget.graphql'
query WidgetFragment($id: ID) {
readWidgetFragment(id: $id) {
...Widget
}
}
To create a GraphQL schema with mocked resolvers and data, I use makeExecutableSchema and addMockFunctionsToSchema from graphql-tools.
To run the query from inside a jest test, my understanding is that I need to use the graphql() function from graphql-js.
This function needs the query as a string, so I tried two different ways, but neither of them worked:
Parse the .graphql file as a normal text file, giving me the raw string (using the jest-raw-loader in my jest config).
This gives me: Failed: Errors in query: Unknown fragment "Widget". when I run the query.
Parse the .graphql file into a query object using jest-transform-graphql. I believe this should be the right approach, because it should resolve any imported fragments properly. However, to execute the query, I need to pass query.loc.source.body to the graphql, which results in the same error message as option 1.

You can use this:
import { print } from 'graphql/language/printer'
import query from './query.gql'
...
print(query)

Use the initial approach with parsing it as a raw text, except:
use a recursive function with a path argument (assuming you could have nested fragments)
which uses regex to extract all imports beforehand to an array (maybe use a nicer pattern :) )
append the rest of the file to a string variable
then loop through imports, resolving the #imports and passing them to itself and appending the result to the string variable
Finally return the result to the main function where you pass it to the graphql()

Yes, this is quite a pickle. Even with imports correctly working (>= v2.1.0 for jest-transform-graphql, they get added to the query.definitions object, which is completely sidestepped when calling graphql with document.loc.source.body as query argument.
On the server end, graphql (function graphqlImpl) will reconstruct the document object using parse(source) - but it'll have zero knowledge of the imported fragment definitions...
As far as I can tell, the best bet is to stamp fragments to the query source before sending it to the server. You'll need to explicitly find all lines starting with #import and replace these with actual text content of the to-be-imported graphql file.
Below is the function that I use. (Not tested for recursive fragments)
// Async wrapper around dynamic `import` function
import { importQuery } from "./queries";
const importAndReplace = async (fileToImport, sourceDocument, line) => {
const doc = await importQuery(fileToImport);
const targetDocument = (await sourceDocument).replace(line, doc.loc.source.body);
return targetDocument;
};
// Inspired by `graphql-tag/loader`
// Uses promises because of async function `importQuery` used
export default async graphqlOperation => {
const { body } = graphqlOperation.loc.source;
const lines = body.split(/\r\n|\r|\n/);
const bodyWithInlineImports = await lines.reduce(
async (accumulator, line) => {
await accumulator;
const lineSplit = line.slice(1).split(" ");
return line[0] === "#" && lineSplit[0] === "import"
? importAndReplace(lineSplit[1].replace(/"/g, ""), accumulator, line)
: Promise.resolve(accumulator);
},
Promise.resolve(body)
);
return bodyWithInlineImports;
};

Related

Combined resolvers always throw "cannot read property 'apply' of undefined" error

I am trying to attach the following guard:
export const isSupplier=(_,__,{me})=>{
me.role==="supplier" ? skip : new ForbiddenError('Not authenticated as supplier.');
}
To a resolver, like this:
addProductNow:combineResolvers(
isSupplier,
async (_,{name,price,description,url,stock},{dataSources,me})=>{
const supplierId=me.id
console.log(supplierId)
const supplier=await dataSources.SupplierAPI.findSupplierById(supplierId)
const newProduct=await dataSources.ProductAPI.addProduct(name,price,description,url,stock,supplierId,{ttlInSeconds:60*20})
return newProduct
}),
Yet it always returns the error "cannot read property 'apply' of undefined". I have tried to log something in the guard , yet it seems like it never gets executed. After removing the guard from the resolver everything works fine and logging 'me' shows the expected value. Am I doing something wrong ? Thanks !
I'm not sure what you're using for your "guards", but I assume it's because you're not doing or returning anything in this function, and it's expecting a function (what it calls "apply" on):
export const isSupplier=(_,__,{me})=>{
me.role==="supplier" ? skip : new ForbiddenError('Not authenticated as supplier.');
}
Did you mean to do this:
export const isSupplier=(_,__,{me})=>{
return me.role==="supplier" ? skip : new ForbiddenError('Not authenticated as supplier.');
}
Follow-up Edit:
From googling some of your variable names, I assume you're using graphql-resolvers.
In the example they provide, they use "arrow functions with implicit return", which is when you
Don't add Braces around the body of your function
Put the whole thing in one expression
The result of the function becomes whatever the expression after the arrow results in:
const isAuthenticated = (root, args, { user }) => user ? skip : new Error('Not authenticated')
This can be converted to using "explicit returns" by adding braces and the return keyword like this:
const isAuthenticated = (root, args, { user }) => {
return user ? skip : new Error('Not authenticated')
}
Your code as documented, however, added the braces but NOT the return and therefore always returns undefined. Looking at the source code of that library, if you don't return EXACTLY skip it resolves to whatever the return value of your function was (in this case undefined).
Can you share more information about your stack trace to show WHAT is trying to call .apply on what?

Transform data after it received from graphql server using vue-composition api

this is my first try of front end developing, so I mostly copy-pasting example is the net.
import {useQuery, useResult} from "#vue/apollo-composable";
export default defineComponent({
name: 'Components',
setup() {
const {result: modulesResult} = useQuery(getModules)
const result = useResult(modulesResult)
return (result)
}
})
I've written the following code and while I can use result in templates (v-for or v-if), I can't find the way how to transform my data.
I want to take the value of result and apply transformation function and pass transformed value to templates. result is vue ref and value is not available if I write ref(result).value right after useResult
const test = ref(result).value
console.log(test) <- undefined
useQuery contains onResult return value, it triggers on data updates, for example
const {onResult} = useQuery(getDerivativeInstruments)
onResult((resultData) => {
instruments.instruments = resultData!!.data.getDerivativeInstruments
instruments.filteredInstruments = resultData!!.data.getDerivativeInstruments
})

Heroku Apollo Server throws "ServerParseError: Unexpected token < in JSON at position 0" only for some queries

I created a GraphQL wrapper for PokeAPI. My queries all work in development fine and most of them work in production. However, I have the following query that works in production for smaller start and end ranges, but throws "ServerParseError: Unexpected token < in JSON at position 0" when I try to query for all of the pokemon with a very large range. This error does not happen in development.
query {
allPokemon(start: 0, end: 964) {
id
name
}
}
My resolver in my GraphQL for allPokemon only hits one REST endpoint and comes back with an array of objects that have the following structure:
{
name: "charmander",
url: "https://pokeapi.co/api/v2/pokemon/4/"
}
My resolver maps over the resulting array to grab the name value and to parse the url value to grab the id number at the end of the string.
Not sure if this is relevant/necessary to include here, but I am using apollo-datasource-rest. I created a class component that extends RESTDataSource that has abstracted out my functions for my GraphQL resolvers. Then I simply call those methods inside of my resolvers. My allPokemon method inside this RESTDataSource component looks like this:
async getAllPokemon(start = 0, end = 964) {
const response = await this.get(`pokemon?offset=${start}&limit=${end}`);
const pokemonIds = response.results.map(pokemon =>
parseUrl(pokemon.url)
);
return pokemonIds;
}
parseUrl is a utils function I created that just takes a url and parses it to grab the number at the end of the url after the last /.
Then in my resolvers, I have the following:
const resolvers = {
Query: {
allPokemon: (parent, args, { dataSources }) => {
return dataSources.pokemonAPI.getAllPokemon(args.start, args.end);
}
}
}
I can't seem to figure out if this is an issue with Heroku or with Apollo Server. My guess was with Heroku since I have no problems in development getting the expected data for all of the queries. I thought perhaps Heroku must have some limitations as far as timing out or how how many iterations of the parsing function it can do, but have been unable to confirm this theory, let alone find a solution. Any help is appreciated!

Access JSON chunk exported from Gatsby Static Query

I have a React Component in a Gatsby app that is using the useStaticQuery hook to pull in data from the GraphQL layer. This component gets used in my application, but it also gets used as part of a JavaScript embed/widget that is created in a separate Webpack configuration.
I don't want the widget to depend on Gatsby, so I've shimmed the relevant bits of Gatsby, but I still need to pass in data to the shim I've created for useStaticQuery. I found that my Gatsby app is generating a file at public/static/d/2250905522.json that contains a perfect representation of the query data, and I'd like to use it like so:
// This file gets substituted when importing from `gatsby`
import queryResult from "../public/static/d/2250905522.json"
export const useStaticQuery = () => queryResult.data
export const graphql = () => {}
This works, but I haven't figured out where this is coming from or how to determine the file name in a way that is deterministic/stable. How is Gatsby determining this file name, and what internals might I use to do the same?
Edit: I found this routine in the Gatsby codebase that appears to be using staticQueryComponent.hash to determine the number. staticQueryComponent is being destructured from store.getState() where store is associated with Redux, but I'm still not sure where the hash is being determined yet.
Edit 2: Found another mention of this in the documentation here. It sounds like hash is a hash of the query itself, so this will change over time if the query changes (which is likely), so I'm still looking for the routine used to compute the hash.
Due to changes in the babel-plugin-remove-graphql-queries, coreyward's (awesome) answer should be updated to:
const { stripIgnoredCharacters } = require('graphql/utilities/stripIgnoredCharacters');
const murmurModule = require('babel-plugin-remove-graphql-queries/murmur');
const murmurhash = typeof murmurModule === 'function' ? murmurModule : murmurModule.murmurhash;
const GATSBY_HASH_SEED = 'abc';
function hashQuery(query) {
const result = murmurhash(stripIgnoredCharacters(query), GATSBY_HASH_SEED).toString();
return result;
}
module.exports = hashQuery;
The changes are:
fix the way murmurhash is imported. Credit to github user veloce, see: https://github.com/birkir/gatsby-source-graphql-universal/pull/16/files
Change to using stripIgnoredCharacters in order to match the updated way that gatsby internally hashes queries by first stripping whitespace and comment lines for efficiency.
Gatsby is using murmurhash with a seed of "abc" to calculate the hash of the full text of the query (including whitespace). This occurs in babel-plugin-remove-graphql-queries.
Since the reused components are isolated from Gatsby, the graphql tagged template literal can be shimmed in order to get the original query for hashing:
// webpack.config.js
module.exports = {
resolve: {
alias: {
gatsby: path.resolve(__dirname, "gatsby-shim.js"),
},
},
}
// gatsby-shim.js
import { murmurhash } from "babel-plugin-remove-graphql-queries/murmur"
import {
stripIgnoredCharacters,
} from "graphql/utilities/stripIgnoredCharacters"
const GATSBY_HASH_SEED = "abc"
const hashQuery = (query) =>
murmurhash(
stripIgnoredCharacters(query),
GATSBY_HASH_SEED
).toString()
export const graphql = query => hashQuery(query.raw[0])
This results in the query hash being passed into useStaticQuery, which can be shimmed similarly to retrieve the cached query from disk.
Also worth noting, newer versions of Gatsby store the StaticQuery result data in public/page-data/sq/d/[query hash].json.
If you're looking to do something similar, I've written up a much longer blog post about the details of this process and the solution I arrived at here.

how to wait a graphql query is finished using apollo?

I have the following code :
ngOnInit() {
this.data = this.apollo.query({ query: ResidentQuery }).subscribe(({data, loading}) => {
this.data = data;
this.loading = loading;
});
if (!this.loading) {
// using this.data
}
}
I want data to be loaded before processed themm after the (!this.loading). It is not the case as loading is asynchronous. How I can wait the data is loaded before using them ?
I am making a graphql query using apollo client. ResidentQuery is a string containing the graphql query.
Thank you for your feedbacks !
I'm not sure how angular works, but I know how it works in react and I know the two are similar.
In react, you need to use componentWillReceiveProps() (EDIT: Now Deprecated), for example:
componentWillReceiveProps(newProps){
if(!newProps.query.loading){
this.setState({data: newProps.query.data})
}
}
From the medium article for how angular and react lifecycles are similar
Basically, the props will be bind automatically by Angular, so I
suppose the setter and getter function will be that part, which gives
you a way to generate complex state or variable, like what you can do
in componentWillReceiveProps().

Resources