Skip to content

Instantly share code, notes, and snippets.

@gund
Last active November 7, 2021 00:34
Show Gist options
  • Save gund/ccbaf471bdeae2413211f509a746bd6f to your computer and use it in GitHub Desktop.
Save gund/ccbaf471bdeae2413211f509a746bd6f to your computer and use it in GitHub Desktop.
Real randomly generated mocks for GraphQL (for unit tests and mocked GraphQL server)

Assuming all files below now you can:

Unit test with mocked queries

import { runMockQuery } from './graphql-test';

const dataQuery = '{ myData { prop1, prop2 } }'; // This is example GQL query string

function renderData(data) {...} // This renders `dataQuery` response

it('featchData should render', async () => {
  const { data } = await runMockQuery(dataQuery); // This is mocked GQL response for query
  
  const result = renderData(data);
  
  expect(result).toRenderData(...);
});

Customise specific bits in mocks

import { runMockQuery } from './graphql-test';
import { mockVal } from './graphql-mocks';

const dataQuery = '{ myData { prop1, prop2 } }'; // This is example GQL query string

function isDataHasProp2(data) { return !!data.myData.prop2 }

it('isDataHasProp2 should return `false` when prop2 does not exists', async () => {
  provideMocks(mockVal({
    Query: mockVal({ prop2: () => null })
  }));
  const { data } = await runMockQuery(dataQuery); // Response will now have both prop1 and prop2
  // But prop2 will be set to `null` while prop1 will be mocked with some real value
  
  const result = renderData(data);
  
  expect(result).toBe(false);
});

Serve complete GraphQL Apollo server with mocks

Just execute file graphql-mock-server.ts with node (ts-node):

$ ts-node graphql-mock-server.ts

Then you can access server at http://localhost:8008, or provide custom PORT env.

With Angular tests

With autogenerated services from apollo-angular

With query like this:

query MyQuery {
  getMy { prop1, prop2 }
}

gql-gen for apollo-angular will generate injectable service named MyQueryGQL.

It then can be used:

import { Component } from '@angular/core';
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { expectQueryAndFlushWithData } from './graphql-test-angular';

import { MyQueryGQL } from './generated/graphql';

@Component({
  selector: 'my-component',
  template: `<p *ngIf="my$ | async as my">prop1 is {{my.data.getMy.prop1}}, prop2 is {{my.data.getMy.prop2}}</p>`
})
class MyComponent {
  my$ = this.myQueryGQL.watch();
  constructor(public myQueryGQL: MyQueryGQL) {}
}

beforeEach(...); // Default setup of TesBed with MyComponent

it('MyComponent should render data from MyQueryGQL', fakeAsync(async () => {
  const fixture = TestBed.createComponent(MyComponent);

  // This will validate that query `MyQueryGQL` was called and also flush mocked data back to component
  // You can get generated data as `mockData` from returned object
  const { mockData } = await expectQueryAndFlushWithData(MyQueryGQL);

  tick(); // This is required since graphql resolves data asynchronously  
  fixture.detectChanges(); // Then we render that data
  
  // Then do your assertions here
  expect(...).toRenderFrom(mockData); // Check if it rendered mock data from `mockData`
}));
// Module for loading Faket.js
let _faker: Faker.FakerStatic;
/**
* Load and cache Faker.js with just english locale
*/
export function faker(): Faker.FakerStatic {
if (!_faker) {
_faker = require('faker/locale/en');
}
return _faker;
}
// GraphQL Mock server that will use mocks same as provided for unit tests
// so you can easily test your frontend with mocked but real data.
import { ApolloServer } from 'apollo-server';
import { buildClientSchema } from 'graphql';
import { getGraphqlMocks } from './graphql-mocks';
const PORT = process.env.PORT || 8008;
const introspection = require('../../src/generated/graphql.schema.json');
const schema = buildClientSchema(introspection);
const mocks = getGraphqlMocks(schema);
const gqlServer = new ApolloServer({
schema,
mocks,
});
gqlServer
.listen(PORT)
.then(() => console.log(`GraphQL Mock Server started on http://localhost:${PORT}`));
// Module that provides custom mocks for GraphQL
// Specific for each project, obviously
import { GraphQLFieldResolver, GraphQLSchema } from 'graphql';
import { IMocks, MockList } from 'graphql-tools';
import { faker } from './faker';
export type MockFactory = () => IMocks;
export const phoneMock = mockVal({
phone: () => faker().phone.phoneNumber('+###########'),
});
export const emailMock = mockVal({
email: () => faker().internet.email(),
});
export const firstLastNameMock = mockVal({
firstName: () => faker().name.firstName(),
lastName: () => faker().name.lastName(),
});
export const usernameMock = mockVal({
username: () => faker().internet.userName(),
});
export const createdDatesMock = mockVal({
createdAt: () =>
faker()
.date.past()
.toISOString(),
updatedAt: () =>
faker()
.date.past()
.toISOString(),
});
export const addressTypeMock = mockVal({
street: () => faker().address.streetName(),
number: () => faker().address.streetSuffix(),
city: () => faker().address.city(),
postalCode: () => faker().address.zipCode,
country: () => faker().address.country(),
fullAddress: () => faker().address.streetAddress(true),
});
export const getResultSetMock = (min: number, max: number, counterMax = 0) =>
mock(() => {
const size = Math.round(faker().random.number({ min, max }));
const total = size + Math.round(faker().random.number(counterMax));
return {
results: mockList(size),
meta: mockVal({ count: () => total }),
};
});
/**
* Collection of different Result Set mocks
*/
export const resultSetMock = {
/**
* Result Set with random results and count might be bigger than result (up to 100)
*/
random: getResultSetMock(0, 10, 100),
/**
* Result Set where size of results and count are the same
*/
exact: getResultSetMock(0, 10),
/**
* Result Set with zero results and zero count
*/
empty: getResultSetMock(0, 0),
};
export const userDataMock = mergeMocks(firstLastNameMock, phoneMock, emailMock);
export const userMock = mergeMocks(userDataMock, createdDatesMock);
export const staticMocks: IMocks = {
Int: () => Math.round(faker().random.number()),
Float: () => faker().random.number(),
String: () => faker().random.words(),
ISO8601Date: () =>
faker()
.date.recent()
.toISOString(),
UserType: userMock,
AddressType: addressTypeMock,
};
/**
* Example of dynamically generated mocks from GQL Schema
*/
export const genResultSetMocks = (schema: GraphQLSchema): IMocks => {
const fields = schema.getQueryType().getFields();
const resultSetFields = Object.keys(fields)
.map(key => fields[key])
.filter(field => field.type.toString().startsWith('ResultSet'));
const mocks = resultSetFields.reduce(
(m, field) => ({ ...m, [field.type.toString()]: resultSetMock.random }),
{},
);
return mocks;
};
export const generateMocks = (schema: GraphQLSchema): IMocks => {
return {
...genResultSetMocks(schema),
};
};
export const getGraphqlMocks = (schema: GraphQLSchema) => {
return { ...staticMocks, ...generateMocks(schema) };
};
export function mockList(len: number | number[], wrappedFunction?: GraphQLFieldResolver<any, any>) {
return () => new MockList(len, wrappedFunction);
}
export function mockVal(val: IMocks, ...mocks: MockFactory[]): MockFactory {
return mock(() => val, ...mocks);
}
export function mock(mockFn: MockFactory, ...mocks: MockFactory[]): MockFactory {
return mergeMocks(mockFn, ...mocks);
}
export function mergeMocks(...mocks: MockFactory[]): () => IMocks {
return (...args) => mocks.reduceRight((obj, mockFn) => ({ ...obj, ...mockFn(...args) }), {});
}
// Extra testing utils for GraphQL in Angular with Apollo projects
import { InjectionToken, Type } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { Query } from 'apollo-angular';
import { ApolloTestingController } from 'apollo-angular/testing';
import { runMockQuery } from './graphql-test';
/** Typed version of {@link TestBed.get()} */
export function testBedGet<T>(type: InjectionToken<T> | Type<T>): T;
export function testBedGet(type: any): any;
export function testBedGet(type: any) {
return TestBed.get(type);
}
/**
* Expects GraphQL query has been made and returns mock data for that query
*/
export async function expectQueryAndGetData<T, V>(queryType: Type<Query<T, V>>) {
const controller = testBedGet(ApolloTestingController) as ApolloTestingController;
const query = testBedGet(queryType);
const op = controller.expectOne(query.document);
const mockData = await runMockQuery(query);
return { query, op, mockData };
}
/**
* Same as {@link expectQueryAndGetData} plus flushes mock data to GraphQL pending operation
*/
export async function expectQueryAndFlushWithData<T, V>(queryType: Type<Query<T, V>>) {
const res = await expectQueryAndGetData(queryType);
res.op.flush(res.mockData);
return res;
}
// Testing utils for GraphQL to run queries and get mocked results
// Allows to override mocks in each test case
import { buildClientSchema, ExecutionResult, graphql, GraphQLSchema, Source } from 'graphql';
import { addMockFunctionsToSchema, IMocks } from 'graphql-tools';
import { getGraphqlMocks } from './graphql-mocks';
const SCHEMA_FILE = './generated/graphql.schema.json';
let schema: GraphQLSchema;
let mocks: IMocks;
let originalMocks: IMocks;
function maybeInitGraphqlMocks() {
if (!schema) {
initGraphqlMocks();
}
}
/**
* Initialize Graphql with generated schema and setup mocks
*/
export function initGraphqlMocks() {
const introspection = require(SCHEMA_FILE);
schema = buildClientSchema(introspection);
mocks = getGraphqlMocks(schema);
addMockFunctionsToSchema({ schema, mocks });
afterEach(resetMocks);
}
/**
* Provide custom mocks for GraphQL queries for current test case
*
* They will be reset after each test
*/
export function provideMocks(_mocks: IMocks) {
maybeInitGraphqlMocks();
originalMocks = mocks;
addMockFunctionsToSchema({ schema, mocks: { ...mocks, ..._mocks } });
}
/**
* Reset mocks for GraphQL to defaults
*/
export function resetMocks() {
if (originalMocks) {
maybeInitGraphqlMocks();
addMockFunctionsToSchema({ schema, mocks: originalMocks });
originalMocks = undefined;
}
}
/**
* Execute GraphQL query and get mock result
*/
export function runMockQuery(query: string | Source): Promise<ExecutionResult<any>> {
maybeInitGraphqlMocks();
return graphql(schema, query);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment