Skip to main content

Faking MS Graph API Responses

This blog post might be outdated!
This blog post was published more than one year ago and might be outdated!
· 3 min read
Stephan Hochdörfer
Head of IT Business Operations

While refactoring and upgrading one of our internal tools that is using the MS Graph API, I experimented with several techniques to fake API responses to simulate requests without actually hitting the MS Graph Production API.

To reduce maintenance overhead, I was looking for a way to avoid creating a separate API service that would return the mocked responses I need. That would be my fallback method if I would not find a better solution. If you want to go this route, you can configure a different baseUrl for the requests via the ClientOptions configuration:

let clientOptions: ClientOptions = {
baseUrl: 'http://mocked-api/'
};
const client = Client.initWithMiddleware(clientOptions);

The Javascript API client comes with support for custom middlewares. A middleware allows you to change the behavior of the client, e.g. you can customize the request or modify the response. That sounded like the perfect way of to return custom responses for my testcases.

I implemented a middleware that gets a list of requests and responses passed. The middleware will check if the current request matches the expected request and then return the response. In case the expected request does not match, an exception is thrown. This is how the implementation looks like:

export class FakeResponseHandlerMiddleware implements Middleware {
private recordedResponses: RecordedResponse[] = [];
/* eslint-disable @typescript-eslint/no-inferrable-types */
private readonly recordedResponseSize: number = 0;

public constructor(responses: RecordedResponse[]) {
this.recordedResponses = responses;
this.recordedResponseSize = responses.length;
}

public async execute(context: Context): Promise<void> {
const recordedResponse = this.recordedResponses.shift();

if((recordedResponse !== undefined) && recordedResponse.matchesRequest(context.request.toString())) {
context.response = recordedResponse.getResponse();
return;
}

const idx: number = this.recordedResponseSize - this.recordedResponses.length;
throw new Error("Request #" + idx + " does not match RecordedRequest or is not defined!");
}
}

The RecordedResponse object is a value object which contains the expected request url and the response that should be returned. The implementation looks like this:

export class RecordedResponse
{
private request: string;
private response: Response;

public constructor(request: string, response: Response) {
this.request = request;
this.response = response;
}

public matchesRequest(request: string): boolean {
// endsWith() is needed because request contains the full url https://graph.microsoft.com/v1.0/me not just
// the path like /me
return request.endsWith(this.request)
}

public getResponse(): Response {
return this.response;
}
}

In my code I can now set up all the responses in the order I need them:

const responses: RecordedResponse[] = [];
responses.push(new RecordedResponse("/me", new Response("hi")));

A new MS Graph API client is configured with the FakeResponseHandlerMiddleware instance:

const clientOptions: ClientOptions = {
middleware: new FakeResponseHandlerMiddleware(responses)
};
const client = Client.initWithMiddleware(clientOptions);

The client instance can be passed to a service or used directly to return the mocked responses:

const response = await client.api("/me").get();
expect(response).toBe('hi');

So far, I am happy with this technique. If I still continue to like it, we might extract the middleware into a separate package and put it up on GitHub.

For testing purposes, it makes more sense to mock the MS Graph Client API, e.g. by using a library like jest. The technique can be helpful for local development when you want to simulate working with an external API, but you don't want to set up a separate Mock API server.