DEV Community

Miguel Niblock
Miguel Niblock

Posted on

Jest setup for Native-Base on Nextjs with React-Native-Web

TLDR

Here's a finished working example on how to configure jest to work with native-base on nextjs and react-native-web. https://github.com/MiguelNiblock/next-jest-nativebase

Intro

Native-base is my favorite ui component library for react-native. It works nicely on web and setup is easy with expo or even react-native-web. However, testing with jest becomes exponentially more challenging once you add this type of library into your project. Testing setup is no longer trivial, you really need to know what you're doing as you'll see below. By looking for solutions online I've come across many comments where people say they rather ditch using their favorite ui library, than forgo proper testing. My intention with this article has several purposes:

  • To help others who have faced this situation in the pursuit of a universal react-native app.
  • To urge library makers to simplify things for their users by either improving their documentation, or by shipping their code pre-compiled.

Background

I installed nativebase on a nextjs project managed by nx monorepo, and jest test fails to render the app due to safeareacontext issues.

I already read the testing article in docs, but it didn't help me. https://docs.nativebase.io/testing because when I add the mock from the setupFile.js, it also complains it cannot use import statement outside a module, this happens when the mock file from safeareacontext imports react.

Since nx makes things more complex, I'm making a minimal repro of the testing issue, in the hopes I can get a minimal sample to work correctly with some help.

templates provided by nativebase have no jest config. none of them. do u guys even test your library?. https://github.com/GeekyAnts/nativebase-templates

nextjs does come with basic jest setup, so I will follow the instructions to install on an existing nextjs project. there's one which uses expo adapter: https://docs.nativebase.io/install-next and another one which uses the native-base next adapter https://docs.nativebase.io/next-adapter . I don't know what the difference is, but I don't think it should affect testing. I'll go for using the native-base/next adapter, since in my nx monorepo the nextjs project is completely independent of the react-native project. If you're using expo, perhaps do the other version.

Research similar issues

this has a potential solution by adding transformignore patterns

https://github.com/GeekyAnts/NativeBase/issues/3045#issuecomment-580956513

this talks about the initial window metrics

https://github.com/GeekyAnts/NativeBase/issues/3907

this one also has the issue of "cannot import statement outside a module"

potential solution jest config https://github.com/GeekyAnts/NativeBase/issues/3105#issuecomment-886034272

another example of adding a transformIgnorepattern to solve the import issue https://github.com/GeekyAnts/NativeBase/issues/3001#issuecomment-718617355

same issue, no solution https://github.com/GeekyAnts/NativeBase/issues/1215

same issue https://stackoverflow.com/questions/45197514/unexpected-token-import-error-crna

another example of a transformignorepattern that helped some people https://github.com/GeekyAnts/NativeBase/issues/977#issuecomment-322541757

https://github.com/GeekyAnts/NativeBase/issues/977#issuecomment-353689768

https://github.com/GeekyAnts/NativeBase/issues/977#issuecomment-354403658

the "correct config for 2022" https://stackoverflow.com/a/71575172

or set up jest for esmodules

https://jestjs.io/docs/ecmascript-modules

but it doesn't support jest.mock in es module for jest

cool syntax to create pattern https://github.com/nrwl/nx/issues/812#issuecomment-429420861

https://github.com/nrwl/nx/issues/812#issuecomment-429488470

nx specific solution with allowJs https://github.com/nrwl/nx/issues/812#issuecomment-429467823

with nx and babel preset env https://github.com/nrwl/nx/issues/812#issuecomment-799930598

jest article from nextjs docs https://nextjs.org/docs/testing#setting-up-jest-with-babel

Start with a nextjs template ready for jest

so I will create a nextjs app from the "with-jest-babel" template. https://github.com/vercel/next.js/tree/canary/examples/with-jest-babel

tests pass initially. app runs.

this is the simple default test I'll be running throughout the following sections:

describe('Home', () => {
  it('renders a heading', () => {
    render(<Home />)

    const heading = screen.getByRole('heading', {
      name: /welcome to next\.js!/i,
    })

    expect(heading).toBeInTheDocument()
  })
})
Enter fullscreen mode Exit fullscreen mode

Install native base on existing nextjs

now I'll follow the instructions to install native-base with next adapter. Follow either this https://docs.nativebase.io/next-adapter or this https://docs.nativebase.io/install-next.

yarn add react-native-web native-base react-native-svg react-native-safe-area-context react-native

yarn add @native-base/next-adapter next-compose-plugins next-transpile-modules next-fonts -D

make pages/_document.ts

export { default } from "@native-base/next-adapter/document"

create next.config.js with initial adapter setup. Template didn't come with one, and native-base instructions say next.config.json. but that's wrong, it's "js".

configure the hello world example in _app.tsx and index.tsx.


Running next after installing native-base

with those changes, starting with a blank nextjs conf, now the nextjs app doesn't start.

error when running yarn dev (next dev) :

Server Error

Error: "Document.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Call Stack
documentInitialProps
file:/home/user/MyProjects/next-jest-nativebase/node_modules/next/dist/server/render.js (822:23)
Enter fullscreen mode Exit fullscreen mode

Honestly I expected Next to at least run after installing that next adapter. Now we must first fix Next, and then fix Jest, to work with native-base. Not fun, but won't give up.

I looked up some solutions online and tried to modify the document from native-base, but I hit a wall, so I ended up deleting the _document.js altogether and then Nextjs could build with native-base hello world text.

this should probably be fixed and done right. but that's a different rabbit hole.

now back to testing.

Jest after installing native-base

the error is:

/home/user/MyProjects/next-jest-nativebase/node_modules/react-native/index.js:14
    import typeof AccessibilityInfo from './Libraries/Components/AccessibilityInfo/AccessibilityInfo';
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module
Enter fullscreen mode Exit fullscreen mode

that means it's trying to import stuff from react-native. I'm not sure if the solution is to alias react-native to react-native-web, or if the solution is to add react-native to transformIgnorePatterns.

Attempts:

1.

transformIgnorePatterns: "/node_modules/?!(react-native)"

SyntaxError: /home/user/MyProjects/next-jest-nativebase/node_modules/react-native/index.js: Unexpected token, expected "{" (14:7)

      12 |
      13 | // Components
    > 14 | import typeof AccessibilityInfo from './Libraries/Components/AccessibilityInfo/AccessibilityInfo';
         |        ^
      15 | import typeof ActivityIndicator from './Libraries/Components/ActivityIndicator/ActivityIndicator';
Enter fullscreen mode Exit fullscreen mode

error was similar, but different. seems like now it's a type transpilation issue. react-native uses flow for types, so perhaps I need a preset or plugin that transpiles flow.

https://babeljs.io/docs/en/babel-preset-flow.html

2.

decided to add the suggestion in react-native-web docs for Jest seup, and add jest config preset: 'react-native-web'. The error was now different but also suggests Flow must be transpiled

SyntaxError: /home/user/MyProjects/next-jest-nativebase/node_modules/react-native/Libraries/Utilities/codegenNativeComponent.js: Unexpected token, expected "from" (14:12)

      12 |
      13 | import requireNativeComponent from '../../Libraries/ReactNative/requireNativeComponent';
    > 14 | import type {HostComponent} from '../../Libraries/Renderer/shims/ReactNativeTypes';
         |             ^
      15 | import UIManager from '../ReactNative/UIManager';
Enter fullscreen mode Exit fullscreen mode

3.

decided to add the "@babel/preset-flow" preset. error was now:

Cannot find module '../Utilities/Platform' from 'node_modules/react-native/Libraries/StyleSheet/processColor.js'

    Require stack:
      node_modules/react-native/Libraries/StyleSheet/processColor.js
      node_modules/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js
      node_modules/react-native/Libraries/ReactNative/getNativeComponentAttributes.js
      node_modules/react-native/Libraries/ReactNative/requireNativeComponent.js
      node_modules/react-native/Libraries/Utilities/codegenNativeComponent.js
      node_modules/react-native-safe-area-context/lib/commonjs/specs/NativeSafeAreaProvider.js
      node_modules/react-native-safe-area-context/lib/commonjs/NativeSafeAreaProvider.js
      node_modules/react-native-safe-area-context/lib/commonjs/SafeAreaContext.js
      node_modules/react-native-safe-area-context/lib/commonjs/index.js
      node_modules/native-base/lib/commonjs/hooks/useSafeArea/index.js
      node_modules/native-base/lib/commonjs/hooks/index.js
      node_modules/native-base/lib/commonjs/components/primitives/Box/index.js
      node_modules/native-base/lib/commonjs/components/composites/AspectRatio/index.js
      node_modules/native-base/lib/commonjs/components/composites/index.js
      node_modules/native-base/lib/commonjs/index.js
      pages/index.tsx
      __tests__/index.test.tsx


    However, Jest was able to find:
        '../Utilities/Platform.android.js'
        '../Utilities/Platform.ios.js'

    You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'json', 'node'].

    See https://jestjs.io/docs/configuration#modulefileextensions-arraystring
Enter fullscreen mode Exit fullscreen mode

interesting. so the Box is making use of safearea, which makes use of some react-native utils. and only native (mobile) ones are provided.

  • so this tells me the type issue is gone, and now the issue is those react-native libs have no place in the Next web build. So perhaps we do have to manually alias react-native-web here in jest.
  • Or to mock safearea.

4.

just as an experiment, I disabled the 'react-native-web' preset, (To see if it's actually doing something) and the error was:

ReferenceError: __DEV__ is not defined

       5 |
       6 | import React from "react"
    >  7 | import { Box } from "native-base"
         | ^
       8 |
       9 | export default function Home() {
      10 |   return (
Enter fullscreen mode Exit fullscreen mode

5.

I reverted that, and added the module name mapper alias in jest, as suggested in docs of react-native-web

https://necolas.github.io/react-native-web/docs/multi-platform/

moduleNameMapper: {
    ...

    "react-native$": "react-native-web",

    ...
  },
Enter fullscreen mode Exit fullscreen mode

got same error as attempt #3. so that means I do need to mock safearea.

there's the nb docs: https://docs.nativebase.io/testing but how do I actually implement that? I'm not testing the provider specifically I just want to make sure the whole app renders correctly. The Home component is calling the safearea from within the native-base internal dependency of component Box -> useSafeArea -> SafeAreaContext.

authors provide more info https://github.com/th3rdwave/react-native-safe-area-context#testing

they suggest adding this to jest setup file.

import mockSafeAreaContext from 'react-native-safe-area-context/jest/mock';

jest.mock('react-native-safe-area-context', () => mockSafeAreaContext);
Enter fullscreen mode Exit fullscreen mode

6.

added the mock, as directed, and got this error:

Cannot find module '../Utilities/Platform' from 'node_modules/react-native/Libraries/StyleSheet/processColor.js'

    Require stack:
      node_modules/react-native/Libraries/StyleSheet/processColor.js
      node_modules/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js
      node_modules/react-native/Libraries/ReactNative/getNativeComponentAttributes.js
      node_modules/react-native/Libraries/ReactNative/requireNativeComponent.js
      node_modules/react-native/Libraries/Utilities/codegenNativeComponent.js
      node_modules/react-native-safe-area-context/lib/commonjs/specs/NativeSafeAreaProvider.js
      node_modules/react-native-safe-area-context/lib/commonjs/NativeSafeAreaProvider.js
      node_modules/react-native-safe-area-context/lib/commonjs/SafeAreaContext.js
      node_modules/react-native-safe-area-context/lib/commonjs/index.js
      node_modules/react-native-safe-area-context/jest/mock.js
      jest.setup.js


    However, Jest was able to find:
        '../Utilities/Platform.android.js'
        '../Utilities/Platform.ios.js'

    You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'json', 'node'].

    See https://jestjs.io/docs/configuration#modulefileextensions-arraystring
Enter fullscreen mode Exit fullscreen mode

this is the same error we got before (attempt #5), but this time it's being initiated from the setup file, that's the only difference. still the issue is that the mock is making use of mobile code and there's no replacement for it on web.

interesting to note, is the fact that the name alias of react-native to react-native-web is not being honored according to the call stack.

7.

I then adjusted the pattern of the module name mapper. From "react-native$": "react-native-web", to "react-native": "react-native-web", ( Just removed the "$")

got new error:

TypeError: Cannot read property 'default' of undefined

       8 | import mockSafeAreaContext from "react-native-safe-area-context/jest/mock"
       9 |
    > 10 | jest.mock("react-native-safe-area-context", () => mockSafeAreaContext)
         |                                                   ^
      11 |
Enter fullscreen mode Exit fullscreen mode

this difference means the "$" was indeed preventing the resolution of rnw in place of react-native.

there's not much info in this error, so at first I was kinda clueless. but after glancing over the rnw package structure, I think it's just because in rnw module, there's no such directory equivalent to this, node_modules/react-native/Libraries/Utilities/codegenNativeComponent.js , (i.e. this is not meant to come from react-native-web, at all) as is expected from react-native.

perhaps if rnw were typescript native, we could get a nicer callstack here too, but its written in flow, so I imagine that's why there's not a more detailed error message.

I think it's an issue with safe area context loading the native version instead of the web version .https://github.com/necolas/react-native-web/issues/2292#issuecomment-1129340357 . necolas (author of react-native-web) said:

Your bundler should be using the web implementation of react-native-safe-area-context instead of the native export. If you continue to have trouble loading the web implementation you should open an issue against their repository

so the problem with the mock they provide is it still loads the actual component including the native code.

const RNSafeAreaContext = jest.requireActual('react-native-safe-area-context');

Enter fullscreen mode Exit fullscreen mode

after inspecting the files in safe area package, there's one from the callstack, NativeSafeAreaProvider, which has file platform extensions. but the one that gets imported in the error callstack (attempt #6) was the default plain ".js", whereas for our purposes it should be the ".web.js" version, perhaps.

8.

so let's tell Jest we want to prioritize importing the .web. version of any file.

set jest moduleFileExtensions setting to moduleFileExtensions: ["web.tsx", "tsx", "web.ts", "ts", "web.jsx", "jsx", "web.js", "js"],

also, remove the rnw alias altogether. // "react-native": "react-native-web",

new error:

`useTheme` must be used within `NativeBaseConfigProvider`

      4 | describe('Home', () => {
      5 |   it('renders a heading', () => {
    > 6 |     render(<Home />)
        |     ^
      7 |
      8 |     const heading = screen.getByRole('heading', {
      9 |       name: /welcome to next\.js!/i,

      at useContext (node_modules/native-base/lib/commonjs/utils/createContext.tsx:22:13)
      at Object.<anonymous> (__tests__/index.test.tsx:6:5)
Enter fullscreen mode Exit fullscreen mode

that looks like it's just a natural usage error from native-base, so that must mean it's working.

the error now is because I'm trying to render a native-base element, without the provider. https://github.com/GeekyAnts/NativeBase/issues/4303#issuecomment-975835832

9.

so now I will add the provider to the test.

  it("renders a heading", () => {
    render(
      <NativeBaseProvider>
        <Home />
      </NativeBaseProvider>
    )
Enter fullscreen mode Exit fullscreen mode

AND FINALLY IT'S WORKING!!!!!!!! I HAVE OFFICIALLY RUN A SUCCESSFUL TEST WITH NATIVE-BASE ON NEXTJS, CONFIGURING THE GUTS OUT OF JEST

I deserve a beer. I've been doing this all day on memorial day.

Top comments (0)