Hi Everyone!!! In this segment, I would like to discuss the unit test of the third part. I am going to use Jest & Supertest for testing purposes.

N.B: Prior knowledge of express, typescript, Postgres, Jest, Supertest, and sound knowledge of GraphQL are required.

In the last part, we implemented GraphQL in some parts of the application. Like cart, order, restaurant, item, etc. And we have also seen the folder structure. Inside the test folder, it will be like this -

...
graphql
- modules
- cart
...
- test
- cart.spec.ts
- cart-api.spec.ts
- cart-source.ts
- order
- item
- public
- restaurent
- test-utils
- apollo-test.server.ts
- supertest-graphql.server.ts
...

Let’s add an npm package first.

npm install --save-dev supertest-graphql

Then create the test server files.

supertest-graphql.utils.ts file

import express, { Express } from "express";
import request, { SuperTestGraphQL, Variables } from 'supertest-graphql';
import apolloTestServer from "./apollo-test.server";

const app: Express = express();
const startTestServer = async () => {
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
await apolloTestServer.start();
apolloTestServer.applyMiddleware({ app, path: '/graphql' });
}
startTestServer();

export const graphQlAgents: SuperTestGraphQL<unknown, Variables> = request(app);

This is pretty similar to the server.ts file and here I am using the SuperTestGraphQL package which basically comes with different built-in functionality to test graphql API.

apollo-test.server.ts file —

import { ApolloServer } from "apollo-server-express";
import { makeExecutableSchema } from "@graphql-tools/schema";
import { constraintDirective, constraintDirectiveTypeDefs, createApolloQueryValidationPlugin } from 'graphql-constraint-directive';
import typeDefs from "../index.schema";
import {
fakeCartData,
fakeOrderResponse,
fakeOrderResponseList,
fakeRestaurentList
} from "../../../tests/utils/fake.service";
import { graphQlFormatError } from "../../graphql-error-format";

const resolvers = {
Query: {
orders: () => fakeOrderResponseList,
restaurents: () => fakeRestaurentList,
cartInfo: () => fakeCartData,
},
Mutation: {
createOrder: () => fakeOrderResponse,
createCart: () => fakeCartData,
updateCart: () => fakeCartData,
deleteCart: () => fakeCartData,
},
};

let schema = makeExecutableSchema({
typeDefs: [constraintDirectiveTypeDefs, typeDefs],
resolvers,
});

schema = constraintDirective()(schema);
const plugins = [
createApolloQueryValidationPlugin({
schema
})
];

const apolloTestServer = new ApolloServer({
schema,
plugins,
mockEntireSchema: false,
formatError: (error: any) => graphQlFormatError(error),
});

export default apolloTestServer;

This is also the same as the apollo.server.ts file but I am using fake data to resolve the request. Because calling a database or real API for testing purposes is not good for data latency.

Now write a few unit tests for the cart module-

cart.spec.ts file -

import 'reflect-metadata';
import apolloTestServer from '../../../test-utils/apollo-test.server';
import { CREATE_CART, DELETE_CART, GET_CART_INFO, UPDATE_CART } from "./cart-source";

describe("Cart GraphQL Test", () => {
it("Create a Cart", async () => {
const result = await apolloTestServer.executeOperation({
query: CREATE_CART,
variables: {
input: {
uuid: "10d9d7e5-8b89-438c-bd75-7f0381cf03fb",
qty: 3,
restaurent_uuid: "59ae9336-435c-47eb-b5d9-4f0669af74c7"
}
}
});

expect(result.data?.createCart).toHaveProperty('uuid');
});

it("Update a Cart", async () => {
const result = await apolloTestServer.executeOperation({
query: UPDATE_CART,
variables: {
input: {
uuid: "8bd66bcf-be3d-4b58-8828-68d14ac1e22b",
qty: 1,
cart_uuid: "59ae9336-435c-47eb-b5d9-4f0669af74c7"
}
}
});

expect(result.data?.updateCart).toHaveProperty('uuid');
});

it("Delete a Cart", async () => {
const result = await apolloTestServer.executeOperation({
query: DELETE_CART,
variables: {
input: {
item_uuid: "8bd66bcf-be3d-4b58-8828-68d14ac1e22b",
cart_uuid: "59ae9336-435c-47eb-b5d9-4f0669af74c7"
}
}
});

expect(result.data?.deleteCart).toHaveProperty('uuid');
});

it("Get a Cart", async () => {
const result = await apolloTestServer.executeOperation({
query: GET_CART_INFO,
variables: {
input: {
uuid: "59ae9336-435c-47eb-b5d9-4f0669af74c7"
}
}
});

expect(result.data?.cartInfo).toHaveProperty('uuid');
});
});

Maybe you are wondering what are these? —

CREATE_CART, DELETE_CART, GET_CART_INFO, UPDATE_CART

Actually, I created this for reusable purposes. They are Query & Mutation.
cart-source.ts file-

import { gql } from "apollo-server-express";

export const CREATE_CART = gql`
mutation createCart($input: CartCreateInput!) {
createCart(input: $input) {
uuid
total_amount
}
}
`;

export const UPDATE_CART = gql`
mutation updateCart($input: CartUpdateInput!) {
updateCart(input: $input) {
uuid
total_amount
}
}
`;

export const DELETE_CART = gql`
mutation deleteCart($input: CartDeleteInput!) {
deleteCart(input: $input) {
uuid
total_amount
}
}
`;

export const GET_CART_INFO = gql`
query Query ($input: CartUuid!){
cartInfo(input: $input) {
uuid
total_amount
}
}
`;

Now, execute this command to see the test results

npm test cart.spec.ts

We just test the schema & mutation of graphql. What about the API? Let’s test them too.
cart-api.spec.ts file-

import 'reflect-metadata';
import { graphQlAgents } from '../../../test-utils/supertest-graphql.server';
import { CREATE_CART, UPDATE_CART, DELETE_CART, GET_CART_INFO } from "./cart-source";

describe("GraphQL - Cart API Test", () => {
it("Create a Cart", async () => {
const { data }: any = await graphQlAgents
.mutate(CREATE_CART)
.set('authorization', 'some token')
.variables({
input: {
uuid: "10d9d7e5-8b89-438c-bd75-7f0381cf03fb",
qty: 3,
restaurent_uuid: "59ae9336-435c-47eb-b5d9-4f0669af74c7"
}
})
.expectNoErrors();

expect(data.createCart).toHaveProperty('uuid');
});

it("Update a Cart", async () => {
const { data }: any = await graphQlAgents
.mutate(UPDATE_CART)
.set('authorization', 'some token')
.variables({
input: {
uuid: "8bd66bcf-be3d-4b58-8828-68d14ac1e22b",
qty: 1,
cart_uuid: "59ae9336-435c-47eb-b5d9-4f0669af74c7"
}
})
.expectNoErrors();

expect(data.updateCart).toHaveProperty('uuid');
});

it("Delete a Cart", async () => {
const { data }: any = await graphQlAgents
.mutate(DELETE_CART)
.set('authorization', 'some token')
.variables({
input: {
item_uuid: "8bd66bcf-be3d-4b58-8828-68d14ac1e22b",
cart_uuid: "59ae9336-435c-47eb-b5d9-4f0669af74c7"
}
})
.expectNoErrors();

expect(data.deleteCart).toHaveProperty('uuid');
});

it("Get a Cart", async () => {
const { data }: any = await graphQlAgents
.query(GET_CART_INFO)
.set('authorization', 'some token')
.variables({
input: {
uuid: "59ae9336-435c-47eb-b5d9-4f0669af74c7"
}
})
.expectNoErrors();

expect(data.cartInfo).toHaveProperty('uuid');
});
});

now execute-

npm test cart-api.spec.ts

You can see the test results.
Let’s add a few more test files. How about the order module?
order.spec.ts file -

import 'reflect-metadata';
import apolloTestServer from '../../../test-utils/apollo-test.server';
import { CREATE_ORDER, ERROR_CREATE_ORDER, ORDER_LIST } from './order-source';

describe("Order GraphQL Test", () => {
it("Get Valid Order List", async () => {
const result = await apolloTestServer.executeOperation({
query: ORDER_LIST
});

expect(result.data?.orders).toEqual(
expect.arrayContaining([
expect.objectContaining({
uuid: expect.any(String)
})
])
);
});

it("Create a Order", async () => {
const result = await apolloTestServer.executeOperation({
query: CREATE_ORDER,
variables: {
input: {
cart_uuid: "e1f3275e-a3f2-4a2d-ac3c-6eb52148fb6b"
}
}
});

expect(result.data?.createOrder).toHaveProperty('uuid');
});

it("Error on a Order", async () => {
const result = await apolloTestServer.executeOperation({
query: ERROR_CREATE_ORDER,
variables: {
input: {
cart_uuid: "e1f3275e-a3f2-4a2d-ac3c-6eb52148fb6b"
}
}
});

expect(result.errors && result.errors[0]).toHaveProperty('message');
});
});

order-api.spec.ts file -

import 'reflect-metadata';
import { graphQlAgents } from '../../../test-utils/supertest-graphql.server';
import { ORDER_LIST, CREATE_ORDER } from './order-source';

describe("GraphQL - Order API Test", () => {
it("Get Valid Order List", async () => {
const { data }: any = await graphQlAgents
.query(
ORDER_LIST
)
.set('authorization', 'some token')
.expectNoErrors();

expect(data.orders).toEqual(
expect.arrayContaining([
expect.objectContaining({
order_item: expect.any(Array)
})
])
);
});

it("Create a Order", async () => {
const { data }: any = await graphQlAgents
.mutate(CREATE_ORDER)
.set('authorization', 'some token')
.variables({
input: {
cart_uuid: "e1f3275e-a3f2-4a2d-ac3c-6eb52148fb6b"
}
}
).expectNoErrors();

expect(data.createOrder).toHaveProperty('serial_number');
})
});

order-source.ts file -

import { gql } from "apollo-server-express";

export const ORDER_LIST = gql`
query OrderList {
orders {
uuid
total_amount
serial_number
restaurent {
uuid
name,
address
}
order_item {
uuid
item {
uuid
name
}
}
}
}
`;

export const CREATE_ORDER = gql`
mutation createOrder($input: OrderInput!) {
createOrder(input: $input) {
uuid
total_amount
serial_number
}
}
`;

export const ERROR_CREATE_ORDER = gql`
mutation createOrder($input: OrderInput!) {
createOrder(input: $input) {
uuid
total_amount
serial_number
test
}
}
`;

I am skipping the rest of the unit test for other modules as well.

Alright !!! Everything seems pretty good. Please suggest to me if you want to add more crucial tests. Here is the branch — FoodApp

Thank You !!!

--

--

Tushar Roy Chowdhury

I am a passionate programmer and always keep eye on new technologies and scrutinize them until getting into shape.