Mocking AadTokenProvider

Unit testing SharePoint Framework code requires mocking many parts of the SDK: spHttpClient, serviceScope, aadTokenProviderFactory. To make the process simpler, my colleague Marcin started a public project called spfx-ut-library and invited me to collaborate. I’ve created the mock for AadTokenProviderFactory. In this article, I describe how to use it with Jest.

Installation and configuration

To execute the library, a test runner is needed. Install Jest and optionally chai, as an assertion library. First, execute the command.

npm i jest @types/jest chai @types/chai ts-jest spfx-ut-library -D

In the meantime, add Jest config to the package.json file.

"jest": {
  "setupFilesAfterEnv": [
    "./tests/setup.ts"
  ],
  "testEnvironment": "jsdom",
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  }
},

Note that testEnvironment is set to jsdom. Some other tests may require a “node” testEnvironment. This line can be replaced with docblock at the beginning of the test file.

/**
* @jest-environment jsdom
*/

The last configuration step is to add {projectFolder}/tests/setup.ts file with the following content.

///<reference types="jest" />
 
import { JestHelper } from "spfx-ut-library/lib/helpers/JestHelper";
JestHelper.registerMocks(jest);

Example

For the purpose of this tutorial I’ve created a TokenProvider class.

import { WebPartContext } from "@microsoft/sp-webpart-base";
import { Log } from "@microsoft/sp-core-library";
 
export class TokenProvider {
    constructor(private context: WebPartContext) {}
 
    public async getToken(resource: string): Promise<string> {
        try {
            const tokenProvider = await this.context.aadTokenProviderFactory.getTokenProvider();
            const token = await tokenProvider.getToken(resource);
            return token;
        } catch (error) {
            Log.error("TokenProvider", error);
            throw error;
        }
    }
}

This class should return a token for the requested resource, or throw and log an error. The class uses three objects that have to be mocked: aadTokenProviderFactory, aadTokenProvider, and Log.

///<reference types="jest" />
import { assert } from "chai";
import { TokenProvider } from "../src/TokenProvider";
import { SPWebPartContextMock } from "spfx-ut-library";
import { Log } from "@microsoft/sp-core-library";
 
jest.mock("@microsoft/sp-core-library", () => ({
    Log: {
        error: jest.fn(),
    },
}));
 
describe("TokenProvider", () => {
    let mockContext = new SPWebPartContextMock();
    const tokenProvider = new TokenProvider(mockContext as any);
 
    afterEach(() => {
        mockContext.aadTokenProviderFactory.aadTokenProviderMock.clearMocks();
        (Log.error as jest.Mock).mockClear();
    });
 
    test("Should return graph token", async () => {
        const TOKEN = "token";
        mockContext.aadTokenProviderFactory.aadTokenProviderMock.registerToken("https://graph.microsoft.com", TOKEN);
        const token = await tokenProvider.getToken("https://graph.microsoft.com");
        assert.equal(token, TOKEN);
    });
 
    test("Should throw error with errorCode = invalid_resource when resource is unavailable", async () => {
        try {
            await tokenProvider.getToken("https://fabricated.service.microsoft.com");
            assert.fail("Should throw error");
        } catch (error) {
            assert.equal(error.errorCode, "invalid_resource");
        }
    });
});

The mock has to be created.

let mockContext = new SPWebPartContextMock();

Then the getToken function can be stubbed.

mockContext.aadTokenProviderFactory.aadTokenProviderMock.registerToken("https://graph.microsoft.com", TOKEN);

Jest mocks Log.error function, as it is a part of a different node package. In the second test case, the error is not mocked. The library will throw a default error which is almost identical to an error thrown by API when the resource is not available.

test("Should throw custom error", async () => {
    mockContext.aadTokenProviderFactory.aadTokenProviderMock.registerError("https://fabricated.service.microsoft.com", {
        errorCode: "custom_error",
        errorMessage: "Custom error message",
        message: "Custom error message",
        name: "Custom error name",
        stack: "Custom error stack",
    });
    try {
        await tokenProvider.getToken("https://fabricated.service.microsoft.com");
        assert.fail("Should throw error");
    } catch (error) {
        assert.equal(error.errorCode, "custom_error");
        assert.equal(error.errorMessage, "Custom error message");
        assert.equal(error.message, "Custom error message");
        assert.equal(error.name, "Custom error name");
        assert.equal(error.stack, "Custom error stack");
    }
});

If you want to learn more about sfpx-ut-library please visit its GitHub. The full example is available in my GitHub repository.