Search code examples
reactjsmicro-frontendreact-testing

Unit testing for micro-frontend react application


I'm trying to write unit tests for a react spa web application that uses micro-frontend architecture. My first step is to write unit tests for the application container.

The application container react component uses a react-router containing a switch with subsequent routes to render the components in the main content area.

Each application is mounted to the application container using JavaScript runtime integration.

I'm using React-Testing-Library and Jest as part of my testing toolset.

I've searched high and low across the Internet and haven't found any useful articles on the issues I'm having. Most of them show a demo of testing a web application that doesn't relate to my scenario.

I have 3 problems that I would like some guidance on.

  1. Since Micro Frontends are composed of multiple layers of components chained together with authentication and other business logic. Should I be testing the "page components" only? Or should I be testing the entire application container starting from the App component? If neither is true, how should I be testing this application?

  2. I tried to test on the page component level to avoid authentication issues and for simplicity sake, but the component contains a component from React Router library, and Jest is complaining that I should not be using <Link> component that isn't enclosed within a <Router> component. However, the <Router> component is present at the parent component level when executed in runtime. How can I tell Jest to "ignore" this problem?

    I could not find a configuration that allows me to ignore this error.

  3. Due to issue #2, I tried to write unit tests by rendering the <App> component, but this component is passed into a Higher Order Component that performs authentication validation. How can I focus on testing the end result instead of the functionality of the authentication HOC just so I can get the component to render and for my tests to execute?


Solution

  • In general, you should test each component and you should make assertions only about the logic contained within that component.

    For instance, let's pretend your App component looks like this:

    const App = () => (
       return (
         <Router>
           <Provider store={store}>
             <ThemeProvider>
                <Main/> <- the main entry point to your application
             </ThemeProvider>
           </Provider>
         </Router>
       );
    );
    

    Then the only thing you need to verify in the test suite for App is that you are rendering the right hierarchy. i.e. you don't care in the App component tests what is contained within Main component, all you care is that App renders Main.

    So how to do this? Mock the Main component in your test suite and make sure your mock is rendered at the right place in the hierarchy. You also need to make sure that it renders a Router a Provider with the proper (probably mocked store) and a ThemeProvider. Again, the contents of each of these components aren't important, just the fact that App renders them in the appropriate hierarchy, as that is the only logic that App contains.

    Once you start to cross boundaries and try to verify the contents of Main in the test suite for App, then it can get very unwieldly very quickly - i.e. if you change an implementation of Main you'll end up breaking your App tests, and that's just wrong (IMO).

    What I have described above is a shallow rendering testing strategy. Some people rail against this strategy (even react-testing-library itself doesn't advocate for it). I have a fair bit of experience writing and testing react code, definitely advocate for shallow rendering/component mocking. Each case is unique of course and it might not always be the best strategy, but in almost all circumstances I've encountered, it allows you to encapsulate your logic into testable chunks and make things very easy to delete/refactor.

    I tried to test on the page component level to avoid authentication issues and for simplicity sake, but the component contains a component from React Router library, and Jest is complaining that I should not be using component that isn't enclosed within a component. However, the component is present at the parent component level when executed in runtime. How can I tell Jest to "ignore" this problem?

    This is a very commonly encountered scenario and the solution is to provide the dependencies your component needs to work in the test setup.

    If your component needs a certain context (like Link needs a NavigationContext), then it's your job to provide that context in the test setup.

    If you component needs to be wrapped in a router, then your test setup looks like this:

    import { render, screen } from '@testing-library/react' 
    import { Router } from 'react-router-dom';
    import { MySubjectComponent } from './MySubjectComponent';
    
    it('should show my component with a Link',() => {
      // provide a Router in the test fixture because you
      // know MySubjectComponent needs it
      render(
        <Router>
          <MySubjectComponent/>
        </Router>
      );
    });
    

    How can I focus on testing the end result instead of the functionality of the authentication HOC just so I can get the component to render and for my tests to execute?

    The answer is always the same regardless of what you are trying to test - mock the dependencies.

    Pleaes be careful, though. I would probably advocate for testing components separately from how they behave in HOCs. Remember, the only job of HOCs is to provide props to a component. You can easily provide those props directly to the component in your test suite to see how it behaves without using a HOC.

    To test the HOC, you need to mock the dependencies of the HOC (the functions, requests, and responses that it uses to perform to authentication) and pass in a mocked component that you can make a simple assertion about the props that the HOC passed to it.

    Good testing is something I could talk about for weeks.