react-testing-libraryazure-ad-msalmsal.jsmsal-react

Mocking authentication when testing MSAL React Apps


Our app is wrapped in the MSAL Authentication Template from @azure/msal-react in a standard way - key code segments are summarized below.

We would like to test app's individual components using react testing library (or something similar). Of course, when a React component such as SampleComponentUnderTest is to be properly rendered by a test as is shown in the simple test below, it must be wrapped in an MSAL component as well.

Is there a proper way to mock the MSAL authentication process for such purposes? Anyway to wrap a component under test in MSAL and directly provide test user's credentials to this component under test? Any references to useful documentation, blog posts, video, etc. to point us in the right direction would be greatly appreciated.

A Simple test

test('first test', () => {
  const { getByText } = render(<SampleComponentUnderTest />);
  const someText = getByText('A line of text');
  expect(someText).toBeInTheDocument();
});

Config

export const msalConfig: Configuration = {
  auth: {
    clientId: `${process.env.REACT_APP_CLIENT_ID}`,
    authority: `https://login.microsoftonline.com/${process.env.REACT_APP_TENANT_ID}`,
    redirectUri:
      process.env.NODE_ENV === 'development'
        ? 'http://localhost:3000/'
        : process.env.REACT_APP_DEPLOY_URL,
  },
  cache: {
    cacheLocation: 'sessionStorage',
    storeAuthStateInCookie: false,
  },
  system: {
    loggerOptions: {
      loggerCallback: (level, message, containsPii) => {
        if (containsPii) {
          return;
        }
        switch (level) {
          case LogLevel.Error:
            console.error(message);
            return;
          case LogLevel.Info:
            console.info(message);
            return;
          case LogLevel.Verbose:
            console.debug(message);
            return;
          case LogLevel.Warning:
            console.warn(message);
            return;
          default:
            console.error(message);
        }
      },
    },
  },
};

Main app component

const msalInstance = new PublicClientApplication(msalConfig);

<MsalProvider instance={msalInstance}>
  {!isAuthenticated && <UnauthenticatedHomePage />}
  {isAuthenticated && <Protected />}
</MsalProvider>

Unauthenticated component

const signInClickHandler = (instance: IPublicClientApplication) => {
  instance.loginRedirect(loginRequest).catch((e) => {
    console.log(e);
  });
};

<UnauthenticatedTemplate>
  <Button onClick={() => signInClickHandler(instance)}>Sign in</Button>
</UnauthenticatedTemplate>

Protected component

<MsalAuthenticationTemplate
  interactionType={InteractionType.Redirect}
  errorComponent={ErrorComponent}
  loadingComponent={LoadingComponent}
>
    <SampleComponentUnderTest />
</MsalAuthenticationTemplate>

Solution

  • I had the same issue as you regarding component's test under msal-react. It took me a couple of days to figure out how to implement a correct auth mock. That's why I've created a package you will find here, that encapsulates all the boilerplate code : https://github.com/Mimetis/msal-react-tester

    Basically, you can do multiple scenaris (user is already logged, user is not logged, user must log in etc ...) in a couple of lines, without having to configure anything and of course without having to reach Azure AD in any cases:

    describe('Home page', () => {
    
      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 logged in', async () => {
    
        msalTester.isLogged();
    
        render(
          <MsalProvider instance={msalTester.client}>
            <MemoryRouter>
              <Layout>
                <HomePage />
              </Layout>
            </MemoryRouter>
          </MsalProvider>,
        );
    
        await msalTester.waitForRedirect();
    
        let allLoggedInButtons = await screen.findAllByRole('button', { name: `${msalTester.activeAccount.name}` });
        expect(allLoggedInButtons).toHaveLength(2);
      });
    
      test('Home page render correctly when user logs in using redirect', async () => {
    
        msalTester.isNotLogged();
        render(
          <MsalProvider instance={msalTester.client}>
            <MemoryRouter>
              <Layout>
                <HomePage />
              </Layout>
            </MemoryRouter>
          </MsalProvider>,
        );
    
        await msalTester.waitForRedirect();
    
        let signin = screen.getByRole('button', { name: 'Sign In - Redirect' });
        userEvent.click(signin);
    
        await msalTester.waitForLogin();
    
        let allLoggedInButtons = await screen.findAllByRole('button', { name: `${msalTester.activeAccount.name}` });
        expect(allLoggedInButtons).toHaveLength(2);
      });