mswjs.io icon indicating copy to clipboard operation
mswjs.io copied to clipboard

MSW loading in react native is not happening

Open turjoy-real opened this issue 2 years ago • 10 comments

Preloading MSW fails to detect if App is registered.

Extra line needs to be added which loads data without MSW first, then recognises MSW.

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

import Config from 'react-native-config';

async function enableMocking() {
  if (Config.APP_ENV !== 'test') {
    return;
  }

  await import('./msw.polyfills');
  const {server} = await import('./src/__mock__/server');
  server.listen();
}

AppRegistry.registerComponent(appName, () => App); // <----

enableMocking().then(() => {
  AppRegistry.registerComponent(appName, () => App);
});

turjoy-real avatar Apr 23 '24 14:04 turjoy-real

Hi, @turjoy-real.

Not sure I fully understand the issue, could you describe it a bit more for me?

The reason AppRegistry.registerComponent() is deferred untul the enableMocking() promise resolves is so your app wouldn't render a component that relies on an HTTP request while MSW hasn't activated yet. In RN case, it mostly comes to the dynamic import promise, which still takes time (the interception itself is instantaneous).

If we call .registerComponent() straight away, wouldn't it start rendering the app?

kettanaito avatar Apr 24 '24 16:04 kettanaito

Yes it starts rendering the app with default APIs. After MSW loads, it then uses MSW mocks.

MSW is supposed to load before AppRegistry. But react native doesn't wait for the promise to resolve loading MSW and throws error saying there's no AppRegistry.

turjoy-real avatar Apr 24 '24 16:04 turjoy-real

But react native doesn't wait for the promise to resolve loading MSW and throws error saying there's no AppRegistry.

Yeah, this is the culprit. RN expects you to call .registerComponent on the same tick.

Do you know if we can defer the promise await to the () => App closure? Does RN support something like this?

App.registerComponent(async () => {
  await enableMocking()
  return App
})

I suppose not but I have zero clue about how RN behaves.

kettanaito avatar Apr 24 '24 16:04 kettanaito

No this is not working too. React Native doesn't expect a async callback function.

This is the new error:

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

This is happening even if I remove the await step.

It's taking the unresolved promise object.

Used this:

AppRegistry.registerComponent(appName, async () => { await enableMocking(); return App; });

Instead of:

AppRegistry.registerComponent(appName, () => App);

turjoy-real avatar Apr 24 '24 17:04 turjoy-real

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

This error suggests the second argument is expected to be a React component. This means we can create a wrapper component and delegate the promise resolution to the state.

App.registerComponent(appName, () => {
  const [isMockingEnabled, setMockingEnabled] = React.useState(false)

  React.useEffect(() => {
    enableMocking().then(() => setMockingEnabled(true))
  }, [])

  if (!isMockingEnabled) return null
  return <App />
})

Can you please give this a try, @turjoy-real? Thanks.

kettanaito avatar Apr 26 '24 11:04 kettanaito

Hi @kettanaito,

Thanks for the idea.

Successfully made MSW APIs the default for all E2E testing API calls.

The changes had to be made in the App.tsx(or App.js) to be able to use hooks.

function App() {
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    enableMocking().then(() => setLoading(false));
  }, []);

  return (
    !loading && <Component/>
  );
}

And for the dynamic imports of msw.polyfills and mock server in typescript,

updated tsconfig.json to use 'ES2022':


{
  "extends": "@react-native/typescript-config/tsconfig.json",
  "compilerOptions": {
    "typeRoots": ["./types"],
    "module": "ES2022"
  }
}

Leaving index.js as it was:



import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

Here is the App commit with the changes.

Thank you for your support.

It was exciting to see it resolved finally with such a simple solution.

Let me know if I can contribute with you in any other issues.

turjoy-real avatar Apr 26 '24 13:04 turjoy-real

That's awesome! 🎉 We need to update the React Native integration docs with this so it's clear to everyone.

kettanaito avatar Apr 26 '24 20:04 kettanaito

Yes, that'll be helpful for everyone!

Let me know if and how I can help.

Should I prepare a draft and post in this thread?

turjoy-real avatar Apr 26 '24 20:04 turjoy-real