├── .github └── workflows │ ├── build.yml │ └── newman.yml ├── .gitignore ├── Automated Testing of GraphQL.pdf ├── GQLDemo.postman_collection.json ├── GraphQL.postman_environment.json ├── README.md ├── __tests__ ├── mutations.js └── queries.js ├── package-lock.json ├── package.json └── src ├── data.js ├── database.js ├── index.js ├── resolvers.js ├── schema.js └── server.js /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: 14 16 | - run: npm ci 17 | - run: npm test 18 | -------------------------------------------------------------------------------- /.github/workflows/newman.yml: -------------------------------------------------------------------------------- 1 | name: Newman 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: 14 14 | - run: npm ci 15 | - run: npm start & 16 | - name: Run postman collections 17 | uses: matt-ball/newman-action@master 18 | with: 19 | collection: GQLDemo.postman_collection.json 20 | environment: GraphQL.postman_environment.json 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | Scratchpad -------------------------------------------------------------------------------- /Automated Testing of GraphQL.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upgundecha/graphql-test/a9fd526e57c644d31c00e56f93d4876e1481a1b3/Automated Testing of GraphQL.pdf -------------------------------------------------------------------------------- /GQLDemo.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "ff6034f1-c993-4769-ae9a-746c5134a608", 4 | "name": "GQLDemo", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Authors", 10 | "event": [ 11 | { 12 | "listen": "test", 13 | "script": { 14 | "id": "fa350ecd-8374-4bc8-99e6-9ff8bd33d309", 15 | "exec": [ 16 | "pm.test(\"Status code is 200\", function () {", 17 | " pm.response.to.have.status(200);", 18 | "});", 19 | "", 20 | "pm.test(\"Verify Author Name\", function () {", 21 | " var responsePayload = pm.response.json();", 22 | " names = responsePayload.data.authors.map((val,index) => val.name)", 23 | " pm.expect(names).to.deep.equal([\"Unmesh Gundecha\",\"Dima Kovalenko\",\"James Bond\"]);", 24 | "});" 25 | ], 26 | "type": "text/javascript" 27 | } 28 | } 29 | ], 30 | "request": { 31 | "method": "POST", 32 | "header": [], 33 | "body": { 34 | "mode": "graphql", 35 | "graphql": { 36 | "query": "{\n authors {\n id\n name\n }\n}", 37 | "variables": "" 38 | } 39 | }, 40 | "url": { 41 | "raw": "http://localhost:4000/graphql", 42 | "protocol": "http", 43 | "host": [ 44 | "localhost" 45 | ], 46 | "port": "4000", 47 | "path": [ 48 | "graphql" 49 | ] 50 | } 51 | }, 52 | "response": [] 53 | }, 54 | { 55 | "name": "Mutation", 56 | "event": [ 57 | { 58 | "listen": "test", 59 | "script": { 60 | "id": "0ab78055-a627-44ab-93bf-458a352d7ece", 61 | "exec": [ 62 | "var jsonData = JSON.parse(responseBody);", 63 | "postman.setEnvironmentVariable(\"data.createAuthor.id\", jsonData.token);" 64 | ], 65 | "type": "text/javascript" 66 | } 67 | } 68 | ], 69 | "request": { 70 | "method": "POST", 71 | "header": [], 72 | "body": { 73 | "mode": "graphql", 74 | "graphql": { 75 | "query": "mutation {\n createAuthor(name: \"Foo Bar\") {\n id\n }\n}", 76 | "variables": "" 77 | } 78 | }, 79 | "url": { 80 | "raw": "http://localhost:4000/graphql", 81 | "protocol": "http", 82 | "host": [ 83 | "localhost" 84 | ], 85 | "port": "4000", 86 | "path": [ 87 | "graphql" 88 | ] 89 | } 90 | }, 91 | "response": [] 92 | }, 93 | { 94 | "name": "Author", 95 | "request": { 96 | "method": "POST", 97 | "header": [], 98 | "body": { 99 | "mode": "graphql", 100 | "graphql": { 101 | "query": "query($id: Int!) {\n author(id:$id) {\n name\n }\n}", 102 | "variables": "{\n\t\"id\": {{id}}\n}" 103 | } 104 | }, 105 | "url": { 106 | "raw": "http://localhost:4000/graphql", 107 | "protocol": "http", 108 | "host": [ 109 | "localhost" 110 | ], 111 | "port": "4000", 112 | "path": [ 113 | "graphql" 114 | ] 115 | } 116 | }, 117 | "response": [] 118 | } 119 | ], 120 | "event": [ 121 | { 122 | "listen": "prerequest", 123 | "script": { 124 | "id": "eec12ae4-27e3-4e45-b6de-8919f505bb9b", 125 | "type": "text/javascript", 126 | "exec": [ 127 | "" 128 | ] 129 | } 130 | }, 131 | { 132 | "listen": "test", 133 | "script": { 134 | "id": "bbe08326-8440-44d6-9c3b-3564bf48cdfa", 135 | "type": "text/javascript", 136 | "exec": [ 137 | "" 138 | ] 139 | } 140 | } 141 | ], 142 | "protocolProfileBehavior": {} 143 | } -------------------------------------------------------------------------------- /GraphQL.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "a1a4d829-0dbc-4b68-8260-1df250dabdd2", 3 | "name": "GraphQL", 4 | "values": [ 5 | { 6 | "key": "id", 7 | "value": "4", 8 | "enabled": true 9 | }, 10 | { 11 | "key": "data.createAuthor.id", 12 | "value": null, 13 | "enabled": true 14 | } 15 | ], 16 | "_postman_variable_scope": "environment", 17 | "_postman_exported_at": "2021-07-10T05:14:57.183Z", 18 | "_postman_exported_using": "Postman/7.23.0" 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-test 2 | 3 | Demo application and tests for API Testing Summit 2021 4 | -------------------------------------------------------------------------------- /__tests__/mutations.js: -------------------------------------------------------------------------------- 1 | const app = require("../src/server"); 2 | const supertest = require("supertest"); 3 | const { stopDatabase } = require("../src/database"); 4 | 5 | const request = supertest(app); 6 | 7 | afterAll(async () => { 8 | await stopDatabase(); 9 | }); 10 | 11 | test("create author mutation", async (done) => { 12 | request 13 | .post("/graphql") 14 | .send({ 15 | query:`mutation { 16 | createAuthor(name: "Foo Bar") { 17 | name 18 | } 19 | }` 20 | }) 21 | .set("Accept", "application/json") 22 | .expect("Content-Type", /json/) 23 | .expect(200) 24 | .end(function (err, res) { 25 | if (err) return done(err); 26 | expect(res.body).toBeInstanceOf(Object); 27 | expect(res.body.data.createAuthor.name).toEqual("Foo Bar"); 28 | done(); 29 | }); 30 | }); -------------------------------------------------------------------------------- /__tests__/queries.js: -------------------------------------------------------------------------------- 1 | const app = require("../src/server"); 2 | const supertest = require("supertest"); 3 | const { stopDatabase } = require("../src/database"); 4 | 5 | const request = supertest(app); 6 | 7 | afterAll(async () => { 8 | await stopDatabase(); 9 | }); 10 | 11 | test("fetch authors", async (done) => { 12 | request 13 | .post("/graphql") 14 | .send({ 15 | query: "{ authors{ id, name} }", 16 | }) 17 | .set("Accept", "application/json") 18 | .expect("Content-Type", /json/) 19 | .expect(200) 20 | .end(function (err, res) { 21 | if (err) return done(err); 22 | expect(res.body).toBeInstanceOf(Object); 23 | expect(res.body.data.authors.length).toEqual(3); 24 | names = res.body.data.authors.map((val,index) => val.name) 25 | expect(names).toEqual(expect.arrayContaining(["Unmesh Gundecha","Dima Kovalenko","James Bond"])); 26 | done(); 27 | }); 28 | }); 29 | 30 | test("fetch author", async (done) => { 31 | request 32 | .post("/graphql") 33 | .send({ 34 | query: "{ author(id: 1) { name } }", 35 | }) 36 | .set("Accept", "application/json") 37 | .expect("Content-Type", /json/) 38 | .expect(200) 39 | .end(function (err, res) { 40 | if (err) return done(err); 41 | expect(res.body).toBeInstanceOf(Object); 42 | expect(res.body.data.author.name).toEqual("Unmesh Gundecha"); 43 | done(); 44 | }); 45 | }); 46 | 47 | test("query that does not exist", async () => { 48 | const response = await request 49 | .post("/graphql") 50 | .send({ 51 | query: "{ reviews { id, rating, comment} }", 52 | }) 53 | .set("Accept", "application/json"); 54 | 55 | expect(response.status).toBe(400); 56 | }); 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "start": "node src/index.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "express": "^4.17.1", 15 | "express-graphql": "^0.12.0", 16 | "graphql": "^15.5.0", 17 | "graphql-playground-middleware-express": "^1.7.22", 18 | "mongodb": "^3.6.6", 19 | "mongodb-memory-server": "^6.9.6" 20 | }, 21 | "devDependencies": { 22 | "jest": "^26.6.3", 23 | "supertest": "^6.1.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | const Authors = [ 2 | { 3 | id: 1, 4 | name: "Unmesh Gundecha", 5 | books: [ 6 | { 7 | id: 1, 8 | title: "Selenium Testing Tools Cookbook - Second Edition 2nd Edition", 9 | published: true, 10 | year: 2015, 11 | link: "https://www.amazon.com/gp/product/B017HP96S2", 12 | rating: 4.6, 13 | author: 1 14 | }, 15 | { 16 | id: 2, 17 | title: "Selenium WebDriver 3 Practical Guide: End-to-end automation testing for web and mobile browsers with Selenium WebDriver, 2nd Edition", 18 | published: true, 19 | year: 2018, 20 | link: "https://www.amazon.com/gp/product/B07BJKWB1J", 21 | rating: 4, 22 | author: 1 23 | } 24 | ] 25 | }, 26 | { 27 | id: 2, 28 | name: "Dima Kovalenko", 29 | books: [ 30 | { 31 | id: 3, 32 | title: "Selenium Design Patterns and Best Practices", 33 | published: true, 34 | year: 2014, 35 | link: "https://www.amazon.com/Selenium-Design-Patterns-Best-Practices-ebook/dp/B00NVDAWXI/", 36 | rating: 3.9, 37 | author: 2 38 | } 39 | ] 40 | }, 41 | { 42 | id: 3, 43 | name: "James Bond", 44 | books: [] 45 | } 46 | ]; 47 | 48 | module.exports = { 49 | Authors 50 | }; -------------------------------------------------------------------------------- /src/database.js: -------------------------------------------------------------------------------- 1 | const { MongoMemoryServer } = require("mongodb-memory-server"); 2 | const { MongoClient } = require("mongodb"); 3 | const data = require("./data"); 4 | 5 | let database = null; 6 | 7 | const mongo = new MongoMemoryServer(); 8 | 9 | async function startDatabase() { 10 | const uri = await mongo.getUri(); 11 | const connection = await MongoClient.connect(uri, { 12 | useNewUrlParser: true, 13 | useUnifiedTopology: true 14 | }); 15 | 16 | //insert test data 17 | if (!database) { 18 | database = connection.db(); 19 | await database.collection("authors").insertMany(data.Authors); 20 | } 21 | 22 | return database; 23 | } 24 | 25 | async function stopDatabase() { 26 | await mongo.stop(); 27 | } 28 | 29 | module.exports = { 30 | startDatabase, 31 | stopDatabase, 32 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const app = require("./server"); 2 | 3 | const port = process.env.PORT || "4000"; 4 | 5 | app.listen(port); 6 | 7 | console.log(`🚀 Server ready at http://localhost:${port}/graphql`); -------------------------------------------------------------------------------- /src/resolvers.js: -------------------------------------------------------------------------------- 1 | const resolvers = { 2 | 3 | authors: async (_, context) => { 4 | const { db } = await context(); 5 | return db 6 | .collection("authors") 7 | .find() 8 | .toArray(); 9 | }, 10 | 11 | author: async ({ id }, context) => { 12 | const { db } = await context(); 13 | return db.collection("authors").findOne({ id }); 14 | }, 15 | 16 | createAuthor: async ({name}, context) => { 17 | const { db } = await context(); 18 | var id = await db.collection("authors").estimatedDocumentCount() + 1; 19 | db.collection("authors").insertOne({id: id, name: name}); 20 | return db.collection("authors").findOne({ id }); 21 | } 22 | 23 | }; 24 | 25 | module.exports = resolvers; -------------------------------------------------------------------------------- /src/schema.js: -------------------------------------------------------------------------------- 1 | const { buildSchema } = require("graphql"); 2 | 3 | const schema = buildSchema(` 4 | type Query { 5 | authors: [Author!]!, 6 | author(id: Int!): Author!, 7 | book(id: Int!): Book! 8 | } 9 | 10 | type Author { 11 | id: ID! 12 | name: String! 13 | books: [Book!] 14 | } 15 | 16 | type Book { 17 | id: ID! 18 | title: String! 19 | published: Boolean! 20 | year: Int! 21 | link: String 22 | rating: Float! 23 | author: Author! 24 | } 25 | 26 | type Mutation { 27 | createAuthor(name: String!): Author 28 | } 29 | `); 30 | 31 | module.exports = schema; 32 | 33 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { graphqlHTTP } = require('express-graphql'); 3 | const schema = require("./schema"); 4 | const resolvers = require("./resolvers"); 5 | const { startDatabase } = require("./database"); 6 | const expressPlayground = require("graphql-playground-middleware-express") 7 | .default; 8 | 9 | // Create a context for holding contextual data (db info in this case) 10 | const context = async () => { 11 | const db = await startDatabase(); 12 | 13 | return { db }; 14 | }; 15 | 16 | const app = express(); 17 | 18 | app.use( 19 | "/graphql", 20 | graphqlHTTP({ 21 | schema, 22 | rootValue: resolvers, 23 | context, 24 | }) 25 | ); 26 | 27 | //Graphql Playground route 28 | app.get("/playground", expressPlayground({ endpoint: "/graphql" })); 29 | 30 | module.exports = app; --------------------------------------------------------------------------------