GraphQL API Service Unit Testing
Unit tests are very important to improve the code base of enterprise-level applications. In this post, I am going to show you the insertion of unit tests to Apollo GraphQL API Service in the Angular application using the Jasmine framework. There are two different approaches to applying GraphQL queries to your code. You can use queries inside your component code. But using an API service as a common class can improve the reusability of the code.
This is the API service that I am going to use to write unit tests.
import { Injectable } from '@angular/core';
import { ApolloQueryResult } from '@apollo/client';
import { Apollo, MutationResult, QueryRef } from 'apollo-angular';
import { DocumentNode } from 'graphql';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ApolloQueryService {
constructor(private apollo: Apollo) {}
query<T>(
queryString: DocumentNode,
vars?: Record<string, unknown>
): QueryRef<T, Record<string, unknown>> {
return this.apollo.watchQuery({
query: queryString,
variables: { ...vars },
});
}
mutate(
queryString: DocumentNode,
input?: any
): Observable<MutationResult<any>> {
return this.apollo.mutate({
mutation: queryString,
variables: { input },
});
}
}
In this example, you can see both watchQuery and mutate queries. In the query method, you need to pass queryString (the query that you need to execute) and the variables (if you want to search by). The mutate method is also similar to the query method. You need to subscribe to both methods to read the output value. First, we need to configure the testbed before writing the test cases. I have imported ApolloTestingModule and provided the ApolloQueryService, the service that we are going to cover. After that, I injected the ApolloTestingController and the ApolloQueryService.
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ApolloTestingModule],
providers: [ApolloQueryService],
});
service = TestBed.inject(ApolloQueryService);
apolloTestingController = TestBed.inject(ApolloTestingController);
});
Now, we can start unit test case writing.
Within the test case, first, we should create the mockServerResponse and assign the data for it. Next, we need to call the method that we want to execute while passing the gql query string and subscribe to the response. This service response should be equal to the mockServiceResponse. Now we can call the expectOne method of the apolloTestingController while passing the gql query string. Using this we can access the operation’s name, variables, document, and extensions. Now we can flush the response (mockServerResponse) that we want to return.
it('should trigger query method', (done) => {
//assign data for mockServerResponse
const mockServerResponse: any = {
data:
{
posts:[
{id:1,title:'Introduction to GraphQL',votes:3,author:{id:1,firstName:'Tom',lastName:'Coleman',__typename:'Author'},__typename:'Post'},
{id:2,title:'Welcome to Apollo',votes:3,author:{id:2,firstName:'Sashko',lastName:'Stubailo',__typename:'Author'},__typename:'Post'},
{id:3,title:'Advanced GraphQL',votes:1,author:{id:2,firstName:'Sashko',lastName:'Stubailo',__typename:'Author'},__typename:'Post'},
{id:4,title:'Launchpad is Cool',votes:7,author:{id:3,firstName:'Mikhail',lastName:'Novikov',__typename:'Author'},__typename:'Post'}
]
}
};
//call the query method and subscribe to the responseservice.query(GET_ALL_POSTS).valueChanges.subscribe((serverResponse) => {
expect(serverResponse).toEqual(mockServerResponse);
done();
});
//pass the query to expectOne method of apolloTestingControllerconst req = apolloTestingController.expectOne(GET_ALL_POSTS);
expect(req.operation.operationName).toBe('allPosts');
//flush is one TestOperation method returned by expectOne
req.flush({
mockServerResponse
});
//verify that no outstanding operations
apolloTestingController.verify();
});
This is the process of writing a test case for the method. This is similar to both query and mutate methods.
import { TestBed } from '@angular/core/testing';
import { gql } from 'apollo-angular';
import {
ApolloTestingController,
ApolloTestingModule,
} from 'apollo-angular/testing';
import { ApolloQueryService } from './graphql-api';
const GET_ALL_POSTS = gql`
query allPosts {
posts {
id
title
votes
author {
id
firstName
lastName
}
}
}
`;
const UPVOTE_POST = gql`
mutation UpVotePost($postId: Int!) {
upvotePost(postId: $postId) {
id
votes
}
}
`;
describe('GraphqlApiQueryService', () => {
let service: ApolloQueryService;
let apolloTestingController: ApolloTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ApolloTestingModule],
providers: [ApolloQueryService],
});
service = TestBed.inject(ApolloQueryService);
apolloTestingController = TestBed.inject(ApolloTestingController);
});
afterEach(() => {
apolloTestingController.verify();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should trigger query method', (done) => {
const mockServerResponse: any = {
data:
{
posts:[
{id:1,title:'Introduction to GraphQL',votes:3,author:{id:1,firstName:'Tom',lastName:'Coleman',__typename:'Author'},__typename:'Post'},
{id:2,title:'Welcome to Apollo',votes:3,author:{id:2,firstName:'Sashko',lastName:'Stubailo',__typename:'Author'},__typename:'Post'},
{id:3,title:'Advanced GraphQL',votes:1,author:{id:2,firstName:'Sashko',lastName:'Stubailo',__typename:'Author'},__typename:'Post'},
{id:4,title:'Launchpad is Cool',votes:7,author:{id:3,firstName:'Mikhail',lastName:'Novikov',__typename:'Author'},__typename:'Post'}
]
}
};
service.query(GET_ALL_POSTS).valueChanges.subscribe((serverResponse) => {
expect(serverResponse).toEqual(mockServerResponse);
done();
});
const req = apolloTestingController.expectOne(GET_ALL_POSTS);
expect(req.operation.operationName).toBe('allPosts');
req.flush({
mockServerResponse
});
apolloTestingController.verify();
});
it('should trigger mutate method', (done) => {
const inputVariable = {
postId: 2,
}
const mockServerResponse = {
data:{upvotePost:{id:2,votes:4,__typename:Post}}
};
service.mutate(UPVOTE_POST, inputVariable).subscribe((serverResponse) => {
expect(serverResponse).toEqual(mockServerResponse);
done();
});
const req = apolloTestingController.expectOne(UPVOTE_POST);
expect(req.operation.operationName).toBe('UpVotePost');
req.flush({
mockServerResponse
});
apolloTestingController.verify();
});
});
Conclusion
The ApolloTestingModule module and ApolloTestingController service can use to simplify the testing process. Using ApolloTestingController,we can mock the calls to the GraphQL endpoint and return the exact results for the query. Inside the test case, first, we need to create the mockServerResponse for the query. After that, we need to call the particular method (query or mutate) with the parameters.
Now we can subscribe to the method and check whether the expect and actual responses are the same. When it receives the query with matching parameters, the method returns the corresponding object that has been flushed to the request (req).
This ApolloTestingController is similar to HttpTestingController and you can simply use this to mock your responses to test GraphQL API Service. Hope this article helps you to improve your testing coverage.
Happy Coding…!
Find Hasini Madhuwanthi on Medium for more tutorials and articles