How to validate graphql schema with fragmented documents - node.js

i was thinking it would be cool to support schema validation at the unit test level so we could be aware of breaking changes to queries when we upgrade our api
i’d like to set up the test so that it supports auto-discovery of any new *.graphql files but in doing so, the jest process thinks the current working directory is in __tests__ so when i evaluate the graphql document manually with the loader, relative fragments in queries like this fail:
#import "./fragments/FullUserData.graphql"
query User(
$zid: String!
) {
user {
userData: get(
zid: $zid
) {
...FullUserData
}
}
}
failure message:
Error: Cannot find module './fragments/FullUserData.graphql' from 'schemaValidation-test.js'"
if i move fragments folder into the __tests__ dir, the test gets happy.
any ideas on what I can do to trick the evaluation to process the fragment as if I was relative to the fragment directory?
__tests__/
- schemaValidation-test.js
queries/
- someQuery.graphql
- fragments/someFragment.graphql
i tried process.chdir() to the queries dir from within jest but no dice
here is the validator:
// __tests__/schemaValidation-test.js
import glob from 'glob'
import { validate } from 'graphql/validation'
import loader from 'graphql-tag/loader'
import schema from 'api/lib/app/graphql/schema'
import path from 'path'
import fs from 'fs'
const gqlDir = path.join(__dirname, '..')
const queryDir = path.join(gqlDir, 'queries', 'shared')
const pattern = `${queryDir}/!(fragments)*.graphql`
const getGraphqlFiles = () => glob.sync(pattern)
describe('api schema', () => {
const files = getGraphqlFiles()
for(var file of files) {
const buffer = fs.readFileSync(file)
let document = (buffer || "").toString()
try {
document = eval(loader.call(
{ cacheable: () => ({}) },
document
))
} catch (e) {
fail(`could not parse ${file}, ${e}`)
}
it(`${file} passes validation`, () => {
const errors = validate(
schema,
document,
)
expect(errors).toEqual([])
})
}
})
How can I tell the loader I am in a different directory relative to the fragment?

I figured this out. The key was to use require instead of fs.readFileSync
import glob from 'glob'
import { validate } from 'graphql/validation'
import schema from 'api/lib/app/graphql/schema'
import path from 'path'
const gqlDir = path.join(__dirname, '..')
const queryDir = path.join(gqlDir, 'queries', 'shared')
const pattern = `${queryDir}/!(fragments)*.graphql`
const getGraphqlFiles = () => glob.sync(pattern)
describe('rent-js-api schema', () => {
const files = getGraphqlFiles()
files.forEach(file => {
/* eslint-disable import/no-dynamic-require */
const document = require(file)
it(`${file} passes validation`, () => {
const errors = validate(
schema,
document,
)
expect(errors).toEqual([])
})
})
})
here is jest.config.json
{
"setupFiles": [
"<rootDir>/test/jest/shim.js",
"<rootDir>/test/jest/setup.js"
],
"moduleDirectories": ["node_modules", "src", "test/jest", "test"],
"collectCoverage": false,
"testMatch": ["**/*-test.js"],
"collectCoverageFrom": [
"**/src/**/*.{js,ts,jsx,tsx}",
"!**/src/**/*-test.js",
"!**/index.{ts,js}",
"!**/src/**/const.{ts,js}",
"!**/ui/theme/**",
"!**/src/**/*.d.{ts,tsx}",
"!**/node_modules/**",
"!**/src/ui/*/themes/**"
],
"coverageDirectory": "./coverage",
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/test/jest/noop-styles",
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/jest/noop-binary",
"^.+\\.html$": "<rootDir>/test/jest/htmlLoader"
},
"moduleFileExtensions": [
"graphql",
"js",
"json",
"ts",
"tsx"
],
"transform": {
"^.+\\.jsx?$": "babel-jest",
"^.+\\.tsx?$": "babel-jest",
"^.+\\.graphql$": "jest-transform-graphql"
},
"testPathIgnorePatterns": [
"<rootDir>/node_modules/",
"^.*__tests__/__helpers__.*"
],
"snapshotSerializers": [
"enzyme-to-json/serializer",
"jest-serializer-html"
]
}

Related

Jest environment has been torn down error in graphql, nodejs project. Used jest.useFakeTimers but still getting the error

Getting ReferenceError: You are trying to import a file after the Jest environment has been torn down From office/test/office.test.ts.
Used jest.useFakeTimers(); after imports, but the issue still exists. Is there any way to resolve it ?
Here is my jest.config.ts
import type { Config } from "#jest/types"
const config: Config.InitialOptions = {
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
testRegex: '(/__test__/.*|(\\.|/)(test|spec))\\.[jt]sx?$',
moduleFileExtensions: [ 'js','ts'],
testEnvironment: "jest-environment-node",
fakeTimers: {
enableGlobally: true,
},
verbose: true
}
export default config
The test file office.test.ts
import {ApolloServer} from 'apollo-server'
import {expect,test,jest} from '#jest/globals';
import officeGql from '../controllers/controller'
jest.useFakeTimers();
test('Office Module Test', async () => {
let typeDefs = officeGql.types
let resolvers = officeGql.resolvers
let testServer = new ApolloServer({
typeDefs,
resolvers,
});
const response = await testServer.executeOperation({
query: `query offices(limit: $limit,offset: $offset){name, address}`,
variables: { limit: 10,offset:0 },
});
expect(response.data).toBeTruthy();
});

Mocking custom Hook with jest and enzyme results in 'xxx is not a function or its return value is not iterable' Error

I am very new to jest and enzyme. In my Project I will use a SPA React based Application. Containing a Context Provider for the data, also several hooks. I Using now Jest (with ts-jest and enzyme)
My jest.config looks like this
module.exports = {
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"snapshotSerializers": ["enzyme-to-json/serializer"]
So my first stept so test UI components works.
Next step was to test componentes with mocked data. But there I got the error described at the bottom.
I have a functional component like this:
export default function CurrentWeather(props: ICurrentWeatherProps) {
const [data, Reload] = useCurrentWeather(props.locationID);
return (<div>......</div>)
}
You will notice the useCurrentWeather hook, here is the code for this:
import { useEffect, useState } from 'react';
import { useLocationState } from '../context/locationContext';
import { ILocationData } from './useLocations';
import _ from 'lodash';
...
export default function useCurrentWeater(locationId: number) {
const locationState = useLocationState();
const Reload = () => { GetData() }
const [Data, SetData] = useState<IWeatherDataInfo>({Id:0,ConditionIcon:'',Date:new Date(),MaxTemp:0, MinTemp:0});
async function GetData() { .... }
useEffect(Reload, [locationState.data, locationId]);
return [Data, Reload] as const;
}
Now I wand to mock these Hook. I tried following
import React from 'react';
import { configure, shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import CurrentWeather from '../components/Weather/CurrentWeather';
import { IWeatherDataInfo } from '../Hooks/useWeaters';
configure({ adapter: new Adapter() });
const mockWeatherReload = jest.fn();
const mockdata: IWeatherDataInfo = { Date: new Date(), ConditionIcon: "", MinTemp: 0, MaxTemp: 10 };
jest.mock('../Hooks/useCurrentWeather', () => ({
useCurrentWeather: jest.fn(()=>{ [mockdata, mockWeatherReload]})
}));
describe("WeatherWidget", () => {
it("RenderOnlyAddButton", () => {
const container = shallow(<CurrentWeather locationID={1} hidden={false} />);
});
});
Now, when I execute this test, I will get this error result:
src/tests/WeatherWidget.test.tsx
● WeatherWidget › RenderOnlyAddButton
TypeError: (0 , _useCurrentWeather.default) is not a function or its return value is not iterable
9 |
10 | export default function CurrentWeather(props: ICurrentWeatherProps) {
> 11 | const [data, Reload] = useCurrentWeather(props.locationID);
| ^
12 | return (
What I'm doing wrong here? Is there what I'm missing?
Try like this:(below should be your functional component's test file)
const mockUseCurrentWeather = jest.fn();
jest.mock("put here the absolute path", () => ({
__esModule: true,
useCurrentWeather: (...args: any) => mockUseCurrentWeather(...args),
}));
describe("WeatherWidget", () => {
beforeEach(() => {
mockUseCurrentWeather.mockClear();
mockUseCurrentWeather.mockReturnValue([undefined, undefined]);
});
it("RenderOnlyAddButton", () => {
mockUseCurrentWeather.mockClear();
mockUseCurrentWeather.mockReturnValue([undefined, undefined]);
const container = shallow(<CurrentWeather locationID={1} hidden={false} />);
});
});

Problems initiating Web API File object in test with Babel and Jest

While trying to upgrade babel to v7 in an existing test setup with Jest and Enzyme, I have encountered a problem where Web API File is empty. Although it responds to methods like myFile.name.
Packages used:
babel => 7.6.4
jest => 24.9.0
babel-jest => 24.9.0
Not really an expert with babel, but this is my config
Babe config
const env = process.env.NODE_ENV;
const isProd = () => env === 'production';
const isDev = () => env === 'development';
const isTest = () => env === 'test';
const babelPresetEnvOptions = () => {
const options = {};
if (isTest()) {
options.targets = { node: 'current' };
} else {
// Disable polyfill transforms
options.useBuiltIns = false;
// Do not transform modules to CommonJS
options.modules = false;
}
if (isProd()) {
options.forceAllTransforms = true;
}
return options;
};
const presets = [
[require.resolve('#babel/preset-env'), babelPresetEnvOptions()],
[require.resolve('#babel/preset-react'), { development: isDev() }],
];
const plugins = [
[require.resolve('#babel/plugin-proposal-decorators'), { legacy: true }],
[require.resolve('#babel/plugin-proposal-class-properties'), { loose: true }],
require.resolve('#babel/plugin-proposal-object-rest-spread'),
require.resolve('#babel/plugin-transform-react-jsx'),
require.resolve('#babel/plugin-transform-runtime'),
];
if (isTest()) {
// Compiles import() to a deferred require()
plugins.push(require.resolve('babel-plugin-dynamic-import-node'));
} else {
// Adds syntax support for import()
plugins.push(require.resolve('#babel/plugin-syntax-dynamic-import'));
}
module.exports = api => {
api.assertVersion('^7.6');
return {
presets,
plugins,
};
};
Jest setup
"jest": {
"rootDir": "./webpack",
"setupFiles": [
"<rootDir>/test/jestsetup.js"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"moduleNameMapper": {
"^.+\\.(css|scss)$": "identity-obj-proxy"
},
"transform": {
"^.+\\.(js|jsx)?$": "babel-jest"
}
},
Problem:
When I try to initiate a file object
const lastModified = 1511256180536;
const myImageFile = new File([''], 'pixel.gif', { type: 'image/gif', lastModified });
console.log(myImageFile); // Also results in => File {}
console.log(imageFile.name); // return 'pixel.gif'
The test snapshot fails as shown below, which I can't explain why.
- file={
- File {
- Symbol(impl): FileImpl {
- "_buffer": Object {
- "data": Array [],
- "type": "Buffer",
- },
- "isClosed": false,
- "lastModified": 1511256180536,
- "name": "pixel.gif",
- "type": "image/gif",
- Symbol(wrapper): [Circular],
- },
- }
- }
+ file={File {}}
Even a hint on this would be great.
Babel debug
#babel/preset-env: `DEBUG` option
Using targets:
{
"node": "12.11"
}
Using modules transform: auto
Using plugins:
syntax-async-generators { "node":"12.11" }
syntax-object-rest-spread { "node":"12.11" }
syntax-json-strings { "node":"12.11" }
syntax-optional-catch-binding { "node":"12.11" }
transform-modules-commonjs { "node":"12.11" }
proposal-dynamic-import { "node":"12.11" }
Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.

Mock 'element' in jest tests

I have a helper which I use for Detox tests which hold abstractions of most commonly occurring actions. Like this.
/**
* Looks for a search input and inputs the query
*/
export const inputSearchQuery = async ({ query = '', placeholderText = '' }) => {
if (placeholderText) {
// look for search input
await expect(element(by.id(TestID.SEARCH_INPUT).withDescendant(by.text(placeholderText)))).toBeVisible();
// tap search input
await element(by.id(TestID.SEARCH_INPUT).withDescendant(by.text(placeholderText))).tap();
// type in query
await element(by.id(TestID.SEARCH_INPUT).withDescendant(by.text(placeholderText))).typeText(query);
} else {
// look for search input
await expect(element(by.id(TestID.SEARCH_INPUT))).toBeVisible();
// tap search input
await element(by.id(TestID.SEARCH_INPUT)).tap();
// type in query
await element(by.id(TestID.SEARCH_INPUT)).typeText(query);
}
};
I want this layer of abstraction to be tested with Jest. So I did the following,
describe('e2e helper', () => {
describe('inputSearchQuery()', () => {
test('should check whether the search input is visible', async () => {
const mockToBeVisible = jest.fn();
await inputSearchQuery({});
expect(mockToBeVisible).toBeCalledTimes(1);
});
});
});
And obviously got the error,
ReferenceError: element is not defined
I understand that element comes from the environment. How do I configure my Jest setup...
"jest": {
"preset": "react-native",
"snapshotSerializers": [
"./node_modules/enzyme-to-json/serializer"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-navigation)"
],
"moduleNameMapper": {
"package.json": "<rootDir>/__mocks__/package.json"
},
"testPathIgnorePatterns": [
"/e2e/"
],
// I'm guessing something like this but doesn't work out of the box
"testEnvironment": "detox"
},
...to address this?

Jest Testing with require modules: ejs-loader

I am playing with the idea of having large static html bundles just loaded into a react component vice typing them all out in jsx. I am currently just experimenting with ejs-loader and html-react-parser to evaluate the feasibility of this. Everything actually renders fine but I cannot get any tests to work with jest for this.
I receive: Cannot find module ejs-loader!./AboutPage.view.ejs from AboutPage.js errors and I am unsure of what to do.
I am currently just working off of react-slingshot as my base for experimenting with this.
The repo for the project is here
The component itself is simple:
import React from 'react';
import Parser from 'html-react-parser';
import '../styles/about-page.css';
const view = require('ejs-loader!./AboutPage.view.ejs')();
// Since this component is simple and static, there's no parent container for it.
const AboutPage = () => {
return (
<div>
{Parser(view)}
</div>
);
};
export default AboutPage;
And the test is:
import React from 'react';
import {shallow} from 'enzyme';
import AboutPage from './AboutPage';
describe('<AboutPage />', () => {
it('should have a header called \'About\'', () => {
const wrapper = shallow(<AboutPage />);
const actual = component.find('h2').text();
const expected = 'About';
expect(actual).toEqual(expected);
});
});
I have read through the docs and similar questions like this. I attempted to use a custom transformer, but I may be misunderstanding something as it doesn't appear to be even called.
Package.json
"jest": {
"moduleNameMapper": {
"\\.(css|scss)$": "identity-obj-proxy",
"^.+\\.(gif|ttf|eot|svg|woff|woff2|ico)$": "<rootDir>/tools/fileMock.js"
},
"transform": {
"^.+\\.js$": "babel-jest",
"\\.(ejs|ejx)$": "<rootDir>/tools/ejx-loader/jest.transformer.js"
}
},
and the transformer itself:
module.exports = {
process(src, filename, config, options){
console.log('????');
return 'module.exports = ' + require(`ejs-loader!./${filename}`);
//return require(`ejs-loader!./${filename}`);
}
};
Can you try changing module name mapper to -
{
"\\.(css|scss)$": "identity-obj-proxy",
"^.+\\.(gif|ttf|eot|svg|woff|woff2|ico)$": "<rootDir>/tools/fileMock.js"
"ejs-loader!(.*)": "$1",
}
This should at least invoke your custom transformer.
Also the custom transformer should be -
const _ = require('lodash');
module.exports = {
process(src, filename, config, options){
console.log('????');
return 'module.exports = ' + _.template(src);
}
};
It doesn't look like you've specified .ejs as a moduleFileExtension.
"jest": {
...
"moduleFileExtensions": ["js", "jsx", "ejs", "ejx"],
...
}
Also, ejs-loader will export the function using cjs syntax for you, so you can do the following in your transformer:
const loader = require('ejs-loader');
module.exports = {process: loader};
Work for me:
"jest": {
"moduleNameMapper": {
'\\.(ejs|ejx)$': '<rootDir>/jest-ejs.transformer.js'
},
moduleFileExtensions: ['js', 'json', 'jsx', 'ejs']
},
In jest-ejs.transformer.js
const loader = require('ejs-loader');
module.exports = {process: loader};

Resources