UNPKG

msal-react-tester

Version:

A npm package to simplify testing react application using msal-react

310 lines (304 loc) 12.8 kB
/*! msal-react-tester v0.3.1 2023-08-10 */ 'use strict'; import { Logger, LogLevel, EventType, InteractionType } from '@azure/msal-browser'; import { MsalReactTesterPlugin } from './MsalReactTesterPlugin.js'; import { defaultTestAccountInfo, defaultTestAuthenticationResult, defaultTestAuthError } from './testerConstants.js'; /** * msal-react tester. Useful to tests your components requiring to be logged in, using msal-react * @example * let msalTester: MsalReactTester; beforeEach(() => { // new instance of msal tester for each test msalTester = new MsalReactTester(); // spy all required msal things msalTester.spyMsal(); }); afterEach(() => { msalTester.resetSpyMsal(); }); test('Home page render correctly when user is not logged', async () => { await msalTester.isNotLogged(); render( <MsalProvider instance={msalTester.client}> <MemoryRouter> <Layout> <HomePage /> </Layout> </MemoryRouter> </MsalProvider>, ); await msalTester.waitForRedirect(); expect(screen.getByText(/Please sign-in/)).toBeInTheDocument(); }); */ class MsalReactTester { /** * Create a new mock IPublicClientApplication instance * @param testAccountInfo test account you want to use. A default is created if null * @param testAuthenticationResult test authentication result you want to use . A default is created is null */ constructor(interationType = 'Redirect', testAccountInfo = defaultTestAccountInfo, testAuthenticationResult = defaultTestAuthenticationResult, testAuthError = defaultTestAuthError) { this.interationType = interationType; this._eventCallbacks = []; this.accounts = []; this.activeAccount = null; this._testRunner = MsalReactTesterPlugin.TestRunner; this._testAccountInfo = testAccountInfo; this._testAuthenticationResult = testAuthenticationResult; this.error = testAuthError; this.client = MsalReactTester.GetNewClient(testAccountInfo, testAuthenticationResult); } async actAwait(interval) { let awaiter = (interval) => new Promise((r, s) => setTimeout(r, interval)); await awaiter(interval); } /** * Initialize the IPublicClientApplication with an active account. */ async isLogged() { this.accounts = [this._testAccountInfo]; this.activeAccount = this._testAccountInfo; // ensuring that render (that should come right after) will not be too fast // and raise an error with act() => .... await this.actAwait(1); } /** * Initialize the IPublicClientApplication with no active account */ async isNotLogged() { this.accounts = []; this.activeAccount = null; // ensuring that render (that should come right after) will not be too fast // and raise an error with act() => .... await this.actAwait(1); } /** * Reset all spy / mocks. Should be used in `afterEach` call: * * @example * afterEach(() => { * msalTester.resetSpyMsal(); * }); */ resetSpyMsal() { this._testRunner.resetAllMocks(); this.accounts = []; this.activeAccount = null; this._eventCallbacks = []; } /** * Wait for login process to be done */ async waitForLogin() { await this._testRunner.waitingFor(() => this._testRunner.expect(this._handleRedirectSpy).toHaveBeenCalledTimes(1)); if (this.interationType === 'Redirect') await this._testRunner.waitingFor(() => this._testRunner.expect(this._loginRedirectSpy).toHaveBeenCalledTimes(1)); else await this._testRunner.waitingFor(() => this._testRunner.expect(this._loginPopupSpy).toHaveBeenCalledTimes(1)); } /** * Wait for redirect handled by MSAL to be done */ async waitForRedirect() { await this._testRunner.waitingFor(() => this._testRunner.expect(this._handleRedirectSpy).toHaveBeenCalledTimes(1)); } /** * Wait for logout process to be done */ async waitForLogout() { await this._testRunner.waitingFor(() => this._testRunner.expect(this._handleRedirectSpy).toHaveBeenCalledTimes(1)); if (this.interationType === 'Redirect') await this._testRunner.waitingFor(() => this._testRunner.expect(this._logoutRedirectSpy).toHaveBeenCalledTimes(1)); else await this._testRunner.waitingFor(() => this._testRunner.expect(this._logoutPopupSpy).toHaveBeenCalledTimes(1)); } /** * Spy and Mocks required MSAL things. Should be used in `beforeEach` call: * * @example * let msalTester: MsalReactTester; beforeEach(() => { // new instance of msal tester for each test msalTester = new MsalReactTester(); // spy all required msal things msalTester.spyMsal(); }); * }); */ spyMsal() { let eventId = 0; this._testRunner.spyOn(this.client, 'addEventCallback').mockImplementation((callbackFn) => { this._eventCallbacks.push(callbackFn); eventId += 1; return eventId.toString(); }); // send a message to say "hey we made redirect start then end" this._handleRedirectSpy = this._testRunner.spyOn(this.client, 'handleRedirectPromise').mockImplementation(() => { const eventStart = { eventType: EventType.HANDLE_REDIRECT_START, interactionType: InteractionType.Redirect, payload: null, error: null, timestamp: 10000, }; this._eventCallbacks.forEach((callback) => { callback(eventStart); }); const eventEnd = { eventType: EventType.HANDLE_REDIRECT_END, interactionType: InteractionType.Redirect, payload: null, error: null, timestamp: 10000, }; this._eventCallbacks.forEach(async (callback) => { callback(eventEnd); }); return Promise.resolve(null); }); this._loginRedirectSpy = this._testRunner.spyOn(this.client, 'loginRedirect').mockImplementation(async (request) => { this.accounts = [this._testAccountInfo]; this.activeAccount = this._testAccountInfo; const eventMessage = { eventType: EventType.LOGIN_SUCCESS, interactionType: InteractionType.Redirect, payload: this._testAuthenticationResult, error: null, timestamp: 10000, }; this._eventCallbacks.forEach((callback) => { callback(eventMessage); }); return Promise.resolve(); }); this._loginPopupSpy = this._testRunner.spyOn(this.client, "loginPopup").mockImplementation(async (request) => { this.accounts = [this._testAccountInfo]; this.activeAccount = this._testAccountInfo; const eventMessage = { eventType: EventType.LOGIN_SUCCESS, interactionType: InteractionType.Popup, payload: this._testAuthenticationResult, error: null, timestamp: 10000 }; this._eventCallbacks.forEach((callback) => { callback(eventMessage); }); return Promise.resolve(this._testAuthenticationResult); }); this._logoutRedirectSpy = this._testRunner.spyOn(this.client, 'logoutRedirect').mockImplementation(async (request) => { this.accounts = []; this.activeAccount = null; const eventMessage = { eventType: EventType.LOGOUT_SUCCESS, interactionType: InteractionType.Redirect, payload: this._testAuthenticationResult, error: null, timestamp: 10000, }; this._eventCallbacks.forEach((callback) => { callback(eventMessage); }); return Promise.resolve(); }); this._logoutPopupSpy = this._testRunner.spyOn(this.client, 'logoutPopup').mockImplementation(async (request) => { this.accounts = []; this.activeAccount = null; const eventMessage = { eventType: EventType.LOGOUT_SUCCESS, interactionType: InteractionType.Popup, payload: this._testAuthenticationResult, error: null, timestamp: 10000, }; this._eventCallbacks.forEach((callback) => { callback(eventMessage); }); return Promise.resolve(); }); this._testRunner.spyOn(this.client, 'getAllAccounts').mockImplementation(() => this.accounts); this._testRunner.spyOn(this.client, 'getActiveAccount').mockImplementation(() => this.activeAccount); this._testRunner.spyOn(this.client, 'setActiveAccount').mockImplementation((account) => (this.activeAccount = account)); } generateFailure() { if (this.interationType === 'Redirect') { if (this._loginRedirectSpy) this._loginRedirectSpy.mockClear(); this._loginRedirectSpy = this._testRunner.spyOn(this.client, 'loginRedirect').mockImplementation(async (request) => { const eventMessage = { eventType: EventType.LOGIN_FAILURE, interactionType: InteractionType.Redirect, payload: null, error: this.error, timestamp: 10000, }; this._eventCallbacks.forEach((callback) => { callback(eventMessage); }); return Promise.resolve(); }); } else { if (this._loginPopupSpy) this._loginPopupSpy.mockClear(); this._loginPopupSpy = this._testRunner.spyOn(this.client, "loginPopup").mockImplementation(async () => { const eventMessage = { eventType: EventType.LOGIN_FAILURE, interactionType: InteractionType.Popup, payload: null, error: this.error, timestamp: 10000 }; this._eventCallbacks.forEach((callback) => { callback(eventMessage); }); return Promise.resolve(null); }); } } } MsalReactTester.GetNewClient = (testAccountInfo, testAuthenticationResult) => { let logger = new Logger({ loggerCallback: (_level, _message, _containsPii) => { }, piiLoggingEnabled: false, logLevel: LogLevel.Error, correlationId: 'mock_test', }); return { initialize: () => Promise.resolve(), acquireTokenPopup: () => Promise.resolve(testAuthenticationResult), acquireTokenRedirect: () => Promise.resolve(), acquireTokenSilent: () => Promise.resolve(testAuthenticationResult), acquireTokenByCode: () => Promise.resolve(testAuthenticationResult), getAllAccounts: () => [testAccountInfo], getAccountByHomeId: () => testAccountInfo, getAccountByUsername: () => testAccountInfo, getAccountByLocalId: () => testAccountInfo, handleRedirectPromise: () => Promise.resolve(testAuthenticationResult), loginPopup: () => Promise.resolve(testAuthenticationResult), loginRedirect: () => Promise.resolve(), logout: () => Promise.resolve(), logoutRedirect: () => Promise.resolve(), logoutPopup: () => Promise.resolve(), ssoSilent: () => Promise.resolve(testAuthenticationResult), addEventCallback: () => null, removeEventCallback: () => { return; }, addPerformanceCallback: () => '', removePerformanceCallback: () => false, enableAccountStorageEvents: () => { return; }, disableAccountStorageEvents: () => { return; }, getTokenCache: () => null, setLogger: (_l) => { return logger; }, setActiveAccount: () => { return; }, getActiveAccount: () => testAccountInfo, initializeWrapperLibrary: () => { return; }, setNavigationClient: () => { return; }, getLogger: () => logger, getConfiguration: () => null, hydrateCache: (...params) => Promise.resolve(), }; }; var MsalReactTester$1 = MsalReactTester; export { MsalReactTester$1 as default }; //# sourceMappingURL=MsalReactTester.js.map