1 December 2018

cover

Creating a Minimal Expo React Native Project with TypeScript and Jest

Expo now supports TypeScript, but adding Jest is still tricky.
Share on:

This article shows the steps to create a minimal Expo project. The project uses TypeScript and Jest. Note that this procedure is based on Expo SDK31 and may be invalid in the future.

Install Expo CLI

As described in the official doc, let’s install the CLI tool.

$ npm install -g expo-cli

Note: I prefer using npx and I use npx in my project, but expo-cli depends on @expo/dev-tools which depends on old graphql. It’s just safer to do global install unless you know the issues that could arise.

Initialize a project

First, simply use the Expo CLI to initialize a project.

$ expo init my-expo-project

Select “blank” for a template. If it’s finished, go into the directory.

$ cd my-expo-project

Minimize the project

If you look at the doc about app.json, the required fields are “name”, “slug”, and “sdkVersion”, but I found “version” is also mandatory. You can safely remove other fields for the start. The resulting app.json is the following.

{
  "expo": {
    "name": "my-expo-project",
    "slug": "my-expo-project",
    "sdkVersion": "31.0.0",
    "version": "1.0.0"
  }
}

Since we use TypeScript, we don’t need babel. The following commands remove it.

$ rm babel.config.js
$ npm uninstall -D babel-preset-expo

We also remove App.js (by rm App.js) and create App.ts. We want to store files in the src directory and App.ts is just an entry point for that.

import App from './src/App';
export default App;

Finally, create the src directory (by mkdir src) and create App.tsx. The simplest one would be like this:

import * as React from 'react';
import { Text, View } from 'react-native';

export default () => (
  <View style={{ marginTop: 50 }}>
    <Text>Hello Expo!</Text>
  </View>
);

One more thing. We need to create config files for TypeScript. These may vary based on requirements, but I would like to have something minimal based on the handbook. The tsconfig.json file for this project is:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es2015", "esnext.asynciterable"],
    "jsx": "react-native",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "types": [
      "jest"
    ]
  }
}

We also use tslint and the tslint.json file for this project is:

{
  "extends": [
    "tslint:recommended",
    "tslint-config-airbnb"
  ],
  "rules": {
    "no-any": true,
    "prefer-array-literal": [true, { "allow-type-parameters": true }],
    "variable-name": [true, "ban-keywords", "check-format", "allow-pascal-case", "allow-leading-underscore"]
  }
}

This includes some of my preferences, especially no-any. Feel free to change it as you want.

To make it work, we need to install TypeScript dependencies.

$ npm install -D typescript tslint tslint-config-airbnb @types/expo @types/react

Running the app

By now, we can run the app by the following command.

$ npm start

This will open up Expo Dev Tools in your browser. (Type “d” in your console, if it doesn’t open.)

screenshot1

Click the “Run on emulator/simulator” or open it in your phone with Expo App. You will see something like this.

screenshot2

This looks minimal, doesn’t it?

Adding Jest

We then add Jest for writing tests. Let’s install dependencies.

$ npm install -D jest jest-expo ts-jest react-test-renderer @types/jest @types/react-test-renderer

What is tricky is the jest configuration. So far, the following config in the “package.json” file seems to work.

  "jest": {
    "preset": "jest-expo",
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js",
      "^.+\\.tsx?$": "ts-jest"
    },
    "testMatch": [
      "**/__tests__/**/*.ts?(x)",
      "**/?(*.)+(spec|test).ts?(x)"
    ],
    "moduleFileExtensions": [
      "js",
      "ts",
      "tsx"
    ],
    "globals": {
      "ts-jest": {
        "tsConfig": {
          "jsx": "react"
        }
      }
    }
  }

This doesn’t look minimal and not very satisfying. Hope it to be improved. As for the “globals” part, see the doc for more information.

Let’s write a snapshot test for App.tsx. This is the file App.spec.tsx in src.

import * as React from 'react';
import * as renderer from 'react-test-renderer';

import App from './App';

describe('App', () => {
  it('snapshot test', () => {
    const tree = renderer.create(
      <App />,
    ).toJSON();
    expect(tree).toMatchSnapshot();
  });
});

We add npm scripts to ease running tests, including TypeScripts tests.

  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "test": "npm run tslint && npm run tsc-test && npm run jest",
    "tslint": "tslint --project .",
    "tsc-test": "tsc --project . --noEmit",
    "jest": "jest"
  },

With this, we can run test simply by:

$ npm test

The result would be something like this:

...
 PASS  src/App.spec.tsx
  App
    ✓ snapshot test (445ms)1 snapshot written.
Snapshot Summary
 › 1 snapshot written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 written, 1 total
Time:        4.707s
Ran all test suites.

Real working example

In this article, we explained the rough procedure to create a minimal Expo app. Some part of the procedure might not be very descriptive enough, and you may find some difficulties reproducing it. The repository below is my project based on what is described in this article.

https://github.com/dai-shi/typescript-expo-apollo-boilerplate

Please check it out if you want to try a real working example. Notice that the goal of this boilerplate is to support the use of React Apollo GraphQL in addition to TypeScript and Jest.

comments powered by Disqus