From 7efea5035bac01aaa2020813420d4b2ade352f38 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Wed, 18 Dec 2024 11:57:08 +0200 Subject: [PATCH 1/8] feat(graphql): query verifies the response --- packages/example-graphql-events/app.ts | 28 +++++++++------- packages/example-graphql-events/package.json | 5 ++- .../src/lib/services/api-base/api-base.ts | 32 ++++++++++--------- .../lib/services/graphql/graphql.service.ts | 9 +++--- yarn.lock | 3 +- 5 files changed, 44 insertions(+), 33 deletions(-) diff --git a/packages/example-graphql-events/app.ts b/packages/example-graphql-events/app.ts index ab597734..cb2505e8 100644 --- a/packages/example-graphql-events/app.ts +++ b/packages/example-graphql-events/app.ts @@ -1,4 +1,9 @@ import { Qminder } from 'qminder-api'; +import gql from 'graphql-tag'; + +interface LocationsResponse { + locations: Location[]; +} interface Location { id: string; @@ -16,25 +21,26 @@ interface TicketCreatedEvent { } async function findFirstLocationId(): Promise { - const result: any = await Qminder.GraphQL.query(`{ - locations { + let result: LocationsResponse; + try { + result = await Qminder.GraphQL.query(gql` + { + locations { id name + } } - }`); - - if (result.errors) { - throw new Error( - `Failed to find locations. Errors: ${JSON.stringify(result.errors)}`, - ); + `); + } catch (e) { + throw new Error(`Failed to find locations. Error: ${JSON.stringify(e)}`); } - if (result.data.locations.length < 1) { + if (result.locations.length < 1) { throw new Error('Account does not have any locations'); } - console.log(`Found ${result.data.locations.length} locations`); - return result.data.locations[0]; + console.log(`Found ${result.locations.length} locations`); + return result.locations[0]; } async function listenForTickets() { diff --git a/packages/example-graphql-events/package.json b/packages/example-graphql-events/package.json index 67f3a4c1..aa996439 100644 --- a/packages/example-graphql-events/package.json +++ b/packages/example-graphql-events/package.json @@ -13,5 +13,8 @@ "typescript": "5.5.4" }, "author": "Qminder (https://www.qminder.com)", - "license": "Apache-2.0" + "license": "Apache-2.0", + "dependencies": { + "graphql-tag": "^2.12.6" + } } diff --git a/packages/javascript-api/src/lib/services/api-base/api-base.ts b/packages/javascript-api/src/lib/services/api-base/api-base.ts index f7041e74..4adae21f 100644 --- a/packages/javascript-api/src/lib/services/api-base/api-base.ts +++ b/packages/javascript-api/src/lib/services/api-base/api-base.ts @@ -121,7 +121,7 @@ export class ApiBase { * @throws when the API key is missing or invalid, or when errors in the * response are found */ - static queryGraph(query: GraphqlQuery): Promise { + static async queryGraph(query: GraphqlQuery): Promise { if (!this.apiKey) { throw new Error('Please set the API key before making any requests.'); } @@ -136,17 +136,21 @@ export class ApiBase { body: JSON.stringify(query), }; - return fetch(`https://${this.apiServer}/graphql`, init) - .then((response: Response) => response.json()) - .then((responseJson: any) => { - if (responseJson.errorMessage) { - throw new Error(responseJson.errorMessage); - } - if (responseJson.errors && responseJson.errors.length > 0) { - throw this.extractGraphQLError(responseJson); - } - return responseJson as Promise; - }); + let response = await fetch(`https://${this.apiServer}/graphql`, init); + let responseJson = await response.json(); + if (!responseJson.data && !responseJson.data) { + throw new Error( + `Server response is not valid GraphQL response. Response: ${JSON.stringify( + responseJson, + )}`, + ); + } + + let graphQLResponse: GraphqlResponse = responseJson; + if (graphQLResponse.errors && graphQLResponse.errors.length > 0) { + throw this.extractGraphQLError(graphQLResponse); + } + return graphQLResponse.data as T; } private static extractError(response: any): Error { @@ -169,9 +173,7 @@ export class ApiBase { return new UnknownError(); } - private static extractGraphQLError(response: { - errors: GraphQLError[]; - }): Error { + private static extractGraphQLError(response: GraphqlResponse): Error { return new SimpleError( response.errors.map((error) => error.message).join('\n'), ); diff --git a/packages/javascript-api/src/lib/services/graphql/graphql.service.ts b/packages/javascript-api/src/lib/services/graphql/graphql.service.ts index cd06074b..27197e9d 100644 --- a/packages/javascript-api/src/lib/services/graphql/graphql.service.ts +++ b/packages/javascript-api/src/lib/services/graphql/graphql.service.ts @@ -8,7 +8,6 @@ import WebSocket, { CloseEvent } from 'isomorphic-ws'; import { Observable, Observer, startWith, Subject } from 'rxjs'; import { distinctUntilChanged, shareReplay } from 'rxjs/operators'; import { ConnectionStatus } from '../../model/connection-status.js'; -import { GraphqlResponse } from '../../model/graphql-response.js'; import { calculateRandomizedExponentialBackoffTime } from '../../util/randomized-exponential-backoff/randomized-exponential-backoff.js'; import { sleepMs } from '../../util/sleep-ms/sleep-ms.js'; import { ApiBase, GraphqlQuery } from '../api-base/api-base.js'; @@ -149,11 +148,11 @@ export class GraphqlService { * @returns a promise that resolves to the query's results, or rejects if the query failed * @throws when the 'query' argument is undefined or an empty string */ - query( - queryDocument: QueryOrDocument, + query( + queryDocument: DocumentNode, variables?: { [key: string]: any }, - ): Promise { - const query = queryToString(queryDocument); + ): Promise { + const query = print(queryDocument); if (!query || query.length === 0) { throw new Error( 'GraphQLService query expects a GraphQL query as its first argument', diff --git a/yarn.lock b/yarn.lock index 7db039a0..7545a71c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4562,7 +4562,7 @@ __metadata: languageName: node linkType: hard -"graphql-tag@npm:2.12.6": +"graphql-tag@npm:2.12.6, graphql-tag@npm:^2.12.6": version: 2.12.6 resolution: "graphql-tag@npm:2.12.6" dependencies: @@ -6767,6 +6767,7 @@ __metadata: version: 0.0.0-use.local resolution: "qminder-graphql-events-example@workspace:packages/example-graphql-events" dependencies: + graphql-tag: "npm:^2.12.6" tsx: "npm:^4.19.2" typescript: "npm:5.5.4" languageName: unknown From ed9d09b45dd1df240e1e0d821d8edd163c65fa77 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Wed, 18 Dec 2024 12:12:51 +0200 Subject: [PATCH 2/8] Fixed tests for API base --- .../lib/services/api-base/api-base.spec.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts b/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts index 60ca2f85..bc02863b 100644 --- a/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts +++ b/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts @@ -436,7 +436,7 @@ describe('ApiBase', () => { it('throws when no query is passed', () => { Qminder.ApiBase.setKey('testing'); - expect(() => (Qminder.ApiBase.queryGraph as any)()).toThrow(); + expect(() => (Qminder.ApiBase.queryGraph as any)()).rejects.toThrow(); }); it('does not throw when no variables are passed', async () => { @@ -452,7 +452,7 @@ describe('ApiBase', () => { it('throws when API key is not defined', () => { fetchSpy.mockReturnValue(new MockResponse(ME_ID.successfulResponse)); - expect(() => Qminder.ApiBase.queryGraph(ME_ID.request)).toThrow(); + expect(() => Qminder.ApiBase.queryGraph(ME_ID.request)).rejects.toThrow(); }); it('sends a correct request', () => { @@ -464,14 +464,14 @@ describe('ApiBase', () => { expect(fetchSpy).toHaveBeenCalledWith(API_URL, ME_ID.expectedFetch); }); - it('resolves with the entire response object, not only response data', (done) => { + it('resolves with response data', (done) => { Qminder.ApiBase.setKey('testing'); fetchSpy.mockImplementation(() => Promise.resolve(new MockResponse(ME_ID.successfulResponse)), ); Qminder.ApiBase.queryGraph(ME_ID.request).then((response) => { - expect(response).toEqual(ME_ID.successfulResponse); + expect(response).toEqual(ME_ID.successfulResponse.data); done(); }); }); @@ -488,18 +488,18 @@ describe('ApiBase', () => { ); }); - it('should resolve with response, even if response has errors', (done) => { + it('should resolve with response, even if response has errors', async () => { Qminder.ApiBase.setKey('testing'); fetchSpy.mockImplementation(() => Promise.resolve(new MockResponse(ERROR_UNDEFINED_FIELD)), ); - Qminder.ApiBase.queryGraph(ME_ID.request).then( - () => done(new Error('Should have errored')), - (error: SimpleError) => { - expect(error.message).toEqual(VALIDATION_ERROR); - done(); - }, + expect(async () => { + await Qminder.ApiBase.queryGraph(ME_ID.request); + }).rejects.toThrow( + `Server response is not valid GraphQL response. Response: ${JSON.stringify( + ERROR_UNDEFINED_FIELD, + )}`, ); }); }); From 54fcaffa0de70737a048d3242eabb4323710603c Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Wed, 18 Dec 2024 12:23:49 +0200 Subject: [PATCH 3/8] More tests --- .../graphql/__tests__/graphql.service.spec.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/javascript-api/src/lib/services/graphql/__tests__/graphql.service.spec.ts b/packages/javascript-api/src/lib/services/graphql/__tests__/graphql.service.spec.ts index 05d8a148..abb2e9ff 100644 --- a/packages/javascript-api/src/lib/services/graphql/__tests__/graphql.service.spec.ts +++ b/packages/javascript-api/src/lib/services/graphql/__tests__/graphql.service.spec.ts @@ -8,7 +8,14 @@ import { GraphqlService } from '../graphql.service'; jest.mock('isomorphic-ws', () => jest.fn()); describe('GraphQL service', function () { - const ME_ID_REQUEST = '{ me { id } }'; + const ME_ID_REQUEST = gql` + { + me { + id + } + } + `; + const ME_ID_REQUEST_STRING = '{ me { id }\n}'; const ME_ID_SUCCESS_RESPONSE: any = { statusCode: 200, data: [ @@ -45,17 +52,17 @@ describe('GraphQL service', function () { }); it('calls ApiBase.queryGraph with the correct parameters', async () => { await graphqlService.query(ME_ID_REQUEST); - const graphqlQuery = { query: ME_ID_REQUEST }; + const graphqlQuery = { query: ME_ID_REQUEST_STRING }; expect(requestStub.calledWith(graphqlQuery)).toBeTruthy(); }); it('calls ApiBase.queryGraph with both query & variables', async () => { const variables = { x: 5, y: 4 }; await graphqlService.query(ME_ID_REQUEST, variables); - const graphqlQuery = { query: ME_ID_REQUEST, variables }; + const graphqlQuery = { query: ME_ID_REQUEST_STRING, variables }; expect(requestStub.calledWith(graphqlQuery)).toBeTruthy(); }); it('collapses whitespace and newlines', async () => { - const query = ` + const query = gql` { me { id @@ -63,7 +70,7 @@ describe('GraphQL service', function () { } `; await graphqlService.query(query); - const graphqlQuery = { query: ME_ID_REQUEST }; + const graphqlQuery = { query: ME_ID_REQUEST_STRING }; expect(requestStub.calledWith(graphqlQuery)).toBeTruthy(); }); From 54f43f872ebb4fef9cb6e7d231071868eef8b250 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Mon, 6 Jan 2025 10:54:57 +0200 Subject: [PATCH 4/8] Refactor --- .../src/lib/model/graphql-response.ts | 4 ++-- .../src/lib/services/api-base/api-base.ts | 23 ++++++++++--------- .../lib/services/graphql/graphql.service.ts | 8 +------ 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/javascript-api/src/lib/model/graphql-response.ts b/packages/javascript-api/src/lib/model/graphql-response.ts index 3e617da1..13f88088 100644 --- a/packages/javascript-api/src/lib/model/graphql-response.ts +++ b/packages/javascript-api/src/lib/model/graphql-response.ts @@ -1,8 +1,8 @@ -export interface GraphqlResponse { +export interface GraphqlResponse { /** An array that contains any GraphQL errors. */ errors?: GraphqlError[]; /** If the data was loaded without any errors, contains the requested object. */ - data?: object; + data?: T; } interface GraphqlError { diff --git a/packages/javascript-api/src/lib/services/api-base/api-base.ts b/packages/javascript-api/src/lib/services/api-base/api-base.ts index 4adae21f..590158ef 100644 --- a/packages/javascript-api/src/lib/services/api-base/api-base.ts +++ b/packages/javascript-api/src/lib/services/api-base/api-base.ts @@ -1,9 +1,9 @@ -import { GraphQLError } from 'graphql'; import { ComplexError } from '../../model/errors/complex-error.js'; import { SimpleError } from '../../model/errors/simple-error.js'; import { UnknownError } from '../../model/errors/unknown-error.js'; import { GraphqlResponse } from '../../model/graphql-response.js'; import { RequestInit } from '../../model/fetch.js'; +import { ResponseValidationError } from '../../model/errors/response-validation-error.js'; type RequestInitWithMethodRequired = Pick & { body?: string | File | object; @@ -137,20 +137,21 @@ export class ApiBase { }; let response = await fetch(`https://${this.apiServer}/graphql`, init); - let responseJson = await response.json(); - if (!responseJson.data && !responseJson.data) { - throw new Error( + let graphQLResponse: GraphqlResponse = await response.json(); + + if (graphQLResponse.errors?.length > 0) { + throw this.extractGraphQLError(graphQLResponse); + } + + if (!graphQLResponse.data) { + throw new ResponseValidationError( `Server response is not valid GraphQL response. Response: ${JSON.stringify( - responseJson, + graphQLResponse, )}`, ); } - let graphQLResponse: GraphqlResponse = responseJson; - if (graphQLResponse.errors && graphQLResponse.errors.length > 0) { - throw this.extractGraphQLError(graphQLResponse); - } - return graphQLResponse.data as T; + return graphQLResponse.data; } private static extractError(response: any): Error { @@ -173,7 +174,7 @@ export class ApiBase { return new UnknownError(); } - private static extractGraphQLError(response: GraphqlResponse): Error { + private static extractGraphQLError(response: GraphqlResponse): Error { return new SimpleError( response.errors.map((error) => error.message).join('\n'), ); diff --git a/packages/javascript-api/src/lib/services/graphql/graphql.service.ts b/packages/javascript-api/src/lib/services/graphql/graphql.service.ts index b66b7a62..bb1f3e31 100644 --- a/packages/javascript-api/src/lib/services/graphql/graphql.service.ts +++ b/packages/javascript-api/src/lib/services/graphql/graphql.service.ts @@ -143,7 +143,7 @@ export class GraphqlService { * }); * ``` * - * @param query required: the query to send, for example `"{ me { selectedLocation } }"` + * @param queryDocument required: the query to send, for example gql`{ me { selectedLocation } }` * @param variables optional: additional variables for the query, if variables were used * @returns a promise that resolves to the query's results, or rejects if the query failed * @throws when the 'query' argument is undefined or an empty string @@ -153,12 +153,6 @@ export class GraphqlService { variables?: { [key: string]: any }, ): Promise { const query = print(queryDocument); - if (!query || query.length === 0) { - throw new Error( - 'GraphQLService query expects a GraphQL query as its first argument', - ); - } - const packedQuery = query.replace(/\s\s+/g, ' ').trim(); const graphqlQuery: GraphqlQuery = { query: packedQuery, From 4deeb18d83ea47202b5573d13f0d84b65de0d42a Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Mon, 6 Jan 2025 12:07:37 +0200 Subject: [PATCH 5/8] Updated tests --- .../lib/services/api-base/api-base.spec.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts b/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts index bc02863b..b7664df2 100644 --- a/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts +++ b/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts @@ -7,6 +7,7 @@ import { ComplexError } from '../../model/errors/complex-error'; import { SimpleError } from '../../model/errors/simple-error'; import { UnknownError } from '../../model/errors/unknown-error'; import { Qminder } from '../../qminder'; +import { ResponseValidationError } from '../../model/errors/response-validation-error'; /** * A function that generates an object with the following keys: @@ -476,30 +477,31 @@ describe('ApiBase', () => { }); }); - it('throws an error when getting errors as response', (done) => { + it('throws an error when getting errors as response', async () => { Qminder.ApiBase.setKey('testing'); fetchSpy.mockImplementation(() => Promise.resolve(new MockResponse(ERROR_UNDEFINED_FIELD)), ); - Qminder.ApiBase.queryGraph(ME_ID.request).then( - () => done(new Error('QueryGraph should have thrown an error')), - () => done(), + expect(async () => { + await Qminder.ApiBase.queryGraph(ME_ID.request); + }).rejects.toThrow( + new SimpleError( + "Validation error of type FieldUndefined: Field 'x' in type 'Account' is undefined @ 'account/x'", + ), ); }); - it('should resolve with response, even if response has errors', async () => { + it('should throw an error when response does not contain any data', async () => { Qminder.ApiBase.setKey('testing'); - fetchSpy.mockImplementation(() => - Promise.resolve(new MockResponse(ERROR_UNDEFINED_FIELD)), - ); + fetchSpy.mockImplementation(() => Promise.resolve(new MockResponse({}))); expect(async () => { await Qminder.ApiBase.queryGraph(ME_ID.request); }).rejects.toThrow( - `Server response is not valid GraphQL response. Response: ${JSON.stringify( - ERROR_UNDEFINED_FIELD, - )}`, + new ResponseValidationError( + `Server response is not valid GraphQL response. Response: {}`, + ), ); }); }); From e858e31070fefbfc04ec42bb27bb4ff9d5ef4565 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Mon, 6 Jan 2025 13:54:42 +0200 Subject: [PATCH 6/8] Clearer GraphqlResponse model --- .../src/lib/model/graphql-response.ts | 27 ++++++++++++++----- .../src/lib/services/api-base/api-base.ts | 26 ++++++++++-------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/packages/javascript-api/src/lib/model/graphql-response.ts b/packages/javascript-api/src/lib/model/graphql-response.ts index 13f88088..2ddee238 100644 --- a/packages/javascript-api/src/lib/model/graphql-response.ts +++ b/packages/javascript-api/src/lib/model/graphql-response.ts @@ -1,11 +1,14 @@ -export interface GraphqlResponse { - /** An array that contains any GraphQL errors. */ - errors?: GraphqlError[]; - /** If the data was loaded without any errors, contains the requested object. */ - data?: T; +export type GraphqlResponse = SuccessResponse | ErrorResponse; + +export interface SuccessResponse { + data: T; +} + +export interface ErrorResponse { + errors: GraphqlError[]; } -interface GraphqlError { +export interface GraphqlError { message: string; errorType: string; validationErrorType?: string; @@ -14,3 +17,15 @@ interface GraphqlError { extensions?: any; locations: { line: number; column: number; sourceName: string }[]; } + +export function isErrorResponse( + response: GraphqlResponse, +): response is ErrorResponse { + return Object.prototype.hasOwnProperty.call(response, 'error'); +} + +export function isSuccessResponse( + response: GraphqlResponse, +): response is SuccessResponse { + return Object.prototype.hasOwnProperty.call(response, 'data'); +} diff --git a/packages/javascript-api/src/lib/services/api-base/api-base.ts b/packages/javascript-api/src/lib/services/api-base/api-base.ts index 590158ef..8bcfc38c 100644 --- a/packages/javascript-api/src/lib/services/api-base/api-base.ts +++ b/packages/javascript-api/src/lib/services/api-base/api-base.ts @@ -1,7 +1,12 @@ import { ComplexError } from '../../model/errors/complex-error.js'; import { SimpleError } from '../../model/errors/simple-error.js'; import { UnknownError } from '../../model/errors/unknown-error.js'; -import { GraphqlResponse } from '../../model/graphql-response.js'; +import { + ErrorResponse, + GraphqlResponse, + isErrorResponse, + isSuccessResponse, +} from '../../model/graphql-response.js'; import { RequestInit } from '../../model/fetch.js'; import { ResponseValidationError } from '../../model/errors/response-validation-error.js'; @@ -139,19 +144,18 @@ export class ApiBase { let response = await fetch(`https://${this.apiServer}/graphql`, init); let graphQLResponse: GraphqlResponse = await response.json(); - if (graphQLResponse.errors?.length > 0) { + if (isErrorResponse(graphQLResponse)) { throw this.extractGraphQLError(graphQLResponse); } - - if (!graphQLResponse.data) { - throw new ResponseValidationError( - `Server response is not valid GraphQL response. Response: ${JSON.stringify( - graphQLResponse, - )}`, - ); + if (isSuccessResponse(graphQLResponse)) { + return graphQLResponse.data; } - return graphQLResponse.data; + throw new ResponseValidationError( + `Server response is not valid GraphQL response. Response: ${JSON.stringify( + graphQLResponse, + )}`, + ); } private static extractError(response: any): Error { @@ -174,7 +178,7 @@ export class ApiBase { return new UnknownError(); } - private static extractGraphQLError(response: GraphqlResponse): Error { + private static extractGraphQLError(response: ErrorResponse): Error { return new SimpleError( response.errors.map((error) => error.message).join('\n'), ); From 95f9ded8100340ea18f0ea747c10dc1cd612617c Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Mon, 6 Jan 2025 13:59:25 +0200 Subject: [PATCH 7/8] Fixed the model --- packages/javascript-api/src/lib/model/graphql-response.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/javascript-api/src/lib/model/graphql-response.ts b/packages/javascript-api/src/lib/model/graphql-response.ts index 2ddee238..dcbaea87 100644 --- a/packages/javascript-api/src/lib/model/graphql-response.ts +++ b/packages/javascript-api/src/lib/model/graphql-response.ts @@ -21,7 +21,7 @@ export interface GraphqlError { export function isErrorResponse( response: GraphqlResponse, ): response is ErrorResponse { - return Object.prototype.hasOwnProperty.call(response, 'error'); + return Object.prototype.hasOwnProperty.call(response, 'errors'); } export function isSuccessResponse( From 62dbb043b74ed82718f7479a7cd008837b5db9d6 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Mon, 6 Jan 2025 14:04:17 +0200 Subject: [PATCH 8/8] Fixed tests --- .../src/lib/services/api-base/api-base.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts b/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts index b7664df2..c68e395c 100644 --- a/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts +++ b/packages/javascript-api/src/lib/services/api-base/api-base.spec.ts @@ -38,8 +38,6 @@ function generateRequestData(query: string, responseData: any): any { body: JSON.stringify(queryObject), }, successfulResponse: { - statusCode: 200, - errors: [], data: [responseData], }, }; @@ -48,7 +46,7 @@ function generateRequestData(query: string, responseData: any): any { const FAKE_RESPONSE = { ok: true, json() { - return { statusCode: 200 }; + return {}; }, }; @@ -494,7 +492,7 @@ describe('ApiBase', () => { it('should throw an error when response does not contain any data', async () => { Qminder.ApiBase.setKey('testing'); - fetchSpy.mockImplementation(() => Promise.resolve(new MockResponse({}))); + fetchSpy.mockImplementation(() => Promise.resolve(FAKE_RESPONSE)); expect(async () => { await Qminder.ApiBase.queryGraph(ME_ID.request);