8

I'm attempting to create an automated UI test suite for my React Native app with Expo. I have looked everywhere for good tutorials but when I get to the actual test writing portion, my tests never even run because of environment issues such as "Unexpected Identifier/Token" on import Icon from... or other stupid issues that I cannot find any tutorials on how to fix them. I literally have spent a week trying to resolve these issues.

I am new to React Native and new to Jest/Detox/Expo

Here's my package.json

{
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "test": "node_modules/.bin/jest test/**/*.spec.js",
    "eject": "expo eject"
  },
  "jest": {
    "verbose": true,
    "preset": "jest-expo"
  },
  "dependencies": {
    "apsl-react-native-button": "^3.1.1",
    "react": "16.5.0",
    "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
    "react-native-camera": "git+https://git@github.com/react-native-community/react-native-camera.git",
    "react-native-camera-roll-picker": "^1.2.3",
    "react-native-elements": "^1.0.0",
    "react-native-fontawesome": "^6.0.1",
    "react-native-is-iphonex": "^1.0.1",
    "react-native-vector-icons": "^6.2.0",
    "react-navigation": "^3.1.5"
  },
  "devDependencies": {
    "babel-preset-expo": "^5.0.0",
    "bunyan-debug-stream": "^2.0.0",
    "detox": "^10.0.9",
    "detox-expo-helpers": "^0.6.0",
    "expo-detox-hook": "^1.0.10",
    "jest-expo": "^32.0.0",
    "react-native-testing-library": "^1.5.0",
    "react-test-renderer": "^16.8.2",
    "babel-jest": "^24.1.0",
    "enzyme": "^3.9.0",
    "@babel/core": "^7.3.3",
    "@expo/vector-icons": "^9.0.0",
    "expo": "^32.0.0",
    "jest": "^24.1.0"
  },
  "private": true,
  "detox": {
    "test-runner": "jest",
    "configurations": {
      "ios.sim": {
        "binaryPath": "bin/Exponent.app",
        "type": "ios.simulator",
        "name": "iPhone X"
      }
    }
  }
}

Here are the errors I'm getting

ip-10-101-32-118:KitchenProject bob.dole$ detox test --loglevel trace
configuration="ios.sim" loglevel="trace" artifactsLocation="artifacts/ios.sim.2019-02-21 21-54-14Z" node_modules/.bin/jest "e2e" --config=e2e/config.json --maxWorkers=1 '--testNamePattern=^((?!:android:).)*$' 
● Deprecation Warning:

  Option "setupTestFrameworkScriptFile" was replaced by configuration "setupFilesAfterEnv", which supports multiple paths.

  Please update your configuration.

  Configuration Documentation:
  https://jestjs.io/docs/configuration.html

 FAIL  e2e/RoomLayout.spec.js
  ● Test suite failed to run

    /Users/bob.dole/KitchenDetail/KitchenProject/node_modules/@expo/vector-icons/FontAwesome.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import glyphMap from './vendor/react-native-vector-icons/glyphmaps/FontAwesome.json';
                                                                                                    ^^^^^^^^

    SyntaxError: Unexpected identifier

    > 1 | import FontAwesomeI from 'react-native-vector-icons/FontAwesome'
        | ^
      2 | import React from 'react'
      3 | 
      4 | export const FontAwesome = props => (

      at ScriptTransformer._transformAndBuildScript (../node_modules/jest/node_modules/jest-runtime/build/ScriptTransformer.js:440:17)
      at Object.<anonymous> (../Components/icons.js:1:1)

 FAIL  e2e/tests/components/RoomLayoutDetox.spec.js
  ● Test suite failed to run

    /Users/bob.dole/KitchenDetail/KitchenProject/node_modules/@expo/vector-icons/FontAwesome.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import glyphMap from './vendor/react-native-vector-icons/glyphmaps/FontAwesome.json';
                                                                                                    ^^^^^^^^

    SyntaxError: Unexpected identifier

    > 1 | import FontAwesomeI from 'react-native-vector-icons/FontAwesome'
        | ^
      2 | import React from 'react'
      3 | 
      4 | export const FontAwesome = props => (

      at ScriptTransformer._transformAndBuildScript (../node_modules/jest/node_modules/jest-runtime/build/ScriptTransformer.js:440:17)
      at Object.<anonymous> (../Components/icons.js:1:1)

Test Suites: 2 failed, 2 total
Tests:       0 total
Snapshots:   0 total
Time:        0.827s
Ran all test suites matching /e2e/i with tests matching "^((?!:android:).)*$".
child_process.js:677
    throw err;
    ^

Error: Command failed: node_modules/.bin/jest "e2e" --config=e2e/config.json --maxWorkers=1 '--testNamePattern=^((?!:android:).)*$' 
    at checkExecSyncError (child_process.js:637:11)
    at Object.execSync (child_process.js:674:13)
    at runJest (/Users/bob.dole/KitchenDetail/KitchenProject/node_modules/detox/local-cli/detox-test.js:166:6)
    at run (/Users/bob.dole/KitchenDetail/KitchenProject/node_modules/detox/local-cli/detox-test.js:86:7)
    at Object.<anonymous> (/Users/bob.dole/KitchenDetail/KitchenProject/node_modules/detox/local-cli/detox-test.js:229:1)
    at Module._compile (internal/modules/cjs/loader.js:738:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:749:10)
    at Module.load (internal/modules/cjs/loader.js:630:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:570:12)
    at Function.Module._load (internal/modules/cjs/loader.js:562:3)

Here's my component file RoomLayout.js

import React, { Component } from 'react';
import { StyleSheet, View, Text, Button } from 'react-native';
import { LayoutButtons } from './LayoutButtons';
import { CameraLauncher } from './CameraLauncher';
import { CommentsLauncher } from './CommentsLauncher';


export class RoomLayout extends Component {
  render() {
    return (
      <View>
        <Text testID='roomLayoutText' style={styles.room}>
          Room Layout{"\n"}
        </Text>
        <Text testID='infoText' style={styles.infoText}>
          Take photos from opposite corners of the room{"\n"}
        </Text>
          <LayoutButtons />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  view: {
      marginTop: 80, 
      textAlign: 'center',
      alignItems: 'center',
      justifyContent: 'center'
  },
  infoText: {
      marginTop: -10,
      fontWeight: 'normal',
      textAlign: 'center',
      fontSize: 12,
      justifyContent: 'center',
      alignSelf: 'center',
      color: 'grey'
  },
  room: {
      marginTop: 15,
      fontWeight: 'bold',
      textAlign: 'center',
      lineHeight: 14,
      fontSize: 15
  }
});

Here's my RoomLayout.spec.js file

import React from 'react';
// import { RoomLayout } from '../Components/RoomLayout';
import { render } from 'react-native-testing-library';

describe('RoomLayout', () => {
   // *** EDIT - I have removed this code ***
   // beforeEach(async () => {
   //   const tree = render(<RoomLayout />);
   //
   // });

  test('should have header and info text', async () => {
      await element(by.text('Room Layout'));
      await element(by.id('infoText'));
      await element(by.id('infoText').and(by.text(' Take photos from opposite corners of the room')));
      await expect(element(by.id('layoutButtonsReference'))).toBeVisible();
    });
  });

skyboyer
  • 15,149
  • 4
  • 41
  • 56
Chase Small
  • 181
  • 2
  • 11
  • Can you explain this line and why are you using it? const tree = render(); – Meeran Tariq Feb 21 '19 at 22:53
  • My mistake, that line was a result of a failed attempt to get the test to successfully run. I have removed it and reran `detox test --loglevel trace` and am experiencing the same result. – Chase Small Feb 21 '19 at 23:31
  • 1
    A couple of things I have noticed is that you haven't set any` testIDs` in your `RoomLayout.js` also the text `'Room Layout'` and `'Take photos from opposite corners of the room'` is missing from the component. With regard to your icons why aren't you using [@expo/vector-icons](https://docs.expo.io/versions/latest/guides/icons/) as *FontAwesome* is included in Expo? – Andrew Feb 22 '19 at 10:10
  • I'm sorry. That was such a n00b move. I copied the contents of the wrong file and pasted it in my question. I have updated the RoomLayout.js component code block. I also was unaware that expo supported FontAwesome already, so thank you so much for mentioning it! – Chase Small Feb 22 '19 at 18:23
  • @Andrew I switched to `import { Iconicons } from `@expo/vector-icons'; but now none of the existing icons are showing up and I'm being told that it couldn't find my icons in the list of available icons and none of the available icons listed work for what I'm needing for my app. For example `frown-o` and `meh-o` are not being found. – Chase Small Feb 22 '19 at 18:52
  • 1
    `FontAwesome` and `Ioncions` are different, they have different icons. You can't just switch them and expect them to be the same. You should be using `import { FontAwesome } from '@expo/vector-icons';` You should check your icons in the directory https://expo.github.io/vector-icons/ – Andrew Feb 22 '19 at 18:58
  • @Andrew Cool, making that change worked great. Thanks for explaining for me. Still very new to RN and am trying to teach myself by doing. – Chase Small Feb 22 '19 at 20:18
  • 1
    No worries. I took the liberty of writing up a tutorial on how to use detox with expo. If you think it’s useful it would be great if you could mark it as the accepted answer. – Andrew Feb 22 '19 at 20:23
  • @Andrew thank you so much for all your help dude. I'm planning on going through your writeup this evening and will absolutely check it once I complete it. Seriously I can't thank you enough for taking so much time to help me along. – Chase Small Feb 22 '19 at 20:50

1 Answers1

17

Setting up Detox with an Expo app. You're probably best placed to start with a clean app that you haven't done anything with yet. You’ll want to make sure you have followed the basic setup (step 1) for getting detox to work on your machine

Install the following devDependencies

npm i -D detox detox-expo-helpers expo-detox-hook jest

Update the package.json

Add the following to your package.json file, this configures detox. You can choose the type of iPhone that you want.

"detox": {
  "configurations": {
    "ios.sim": {
      "binaryPath": "bin/Exponent.app",
      "type": "ios.simulator",
      "name": "iPhone X"
    }
  },
  "test-runner": "jest"
 }

In the scripts section add the following:

"scripts": {
  "e2e": "detox test --configuration ios.sim"
}

This will allow us to run the detox test but using npm run e2e

Setup your first test

Run the following to set up your first test

detox init -r jest

This will add a folder called e2e in your project. You will find three files inside it

  • config.json
  • firstTest.spec.js
  • init.js

firstTest.spec.js is a sample test. You will need to make the following changes to it.

const { reloadApp } = require('detox-expo-helpers');

You also need to change the following line

await device.reloadReactNative();

to

await reloadApp();

Add the Expo Client to your project

  • Download the Expo Client iOS App from Expo.io/tools.
  • Unzip the iOS IPA and rename the folder to Exponent.app. It'll have a file icon but will still be a folder.
  • Create bin folder and put Exponent.app inside so it matches the binaryPath set above.

Or alternatively you could use the following script, create a file and name it setup.sh in your project root directory, copy the contents and then run it (you will probably need to give it permission to run which you can do by running chmod +x setup.sh first, then you can run it using ./setup.sh).

#!/bin/bash -e

# query expo.io to find most recent ipaUrl
IPA_URL=`curl https://expo.io/--/api/v2/versions |  python -c 'import sys, json; print json.load(sys.stdin)["iosUrl"]'`

# download tar.gz
TMP_PATH=/tmp/exponent.tar.gz
wget -O $TMP_PATH $IPA_URL

# recursively make app dir
APP_PATH=bin/Exponent.app
mkdir -p $APP_PATH

# unzip tar.gz into APP_PATH
tar -C $APP_PATH -xzf $TMP_PATH

This script does the same as the above steps.

Run your first test

Start the packager with expo start -c

Launch the simulator that you plan to use for your test (so if you picked an iPhone X, launch the iPhone X etc).

Then in your terminal you can run npm run e2e if you added the script or you can run detox test.

What you will find is that your test will fail. This is ok and to be expected.

Now if you want to make your test pass you need to implement all of the test cases that exist in the firstTest.spec.js or you can scrap those and write your own.

Links

Make your tests pass

If you want to make your tests pass you can update the following files and you should get 3 passing tests.

firstTest.spec.js

const { reloadApp } = require('detox-expo-helpers');

describe('Example', () => {
  beforeEach(async () => {
    await reloadApp();
  });

  it('should have welcome screen', async () => {
    await expect(element(by.id('welcome'))).toBeVisible();
  });

  it('should show hello screen after tap', async () => {
    await element(by.id('hello_button')).tap();
    await expect(element(by.label('Hello!!!'))).toBeVisible();
  });

  it('should show world screen after tap', async () => {
    await element(by.id('world_button')).tap();
    await expect(element(by.label('World!!!'))).toBeVisible();
  });
});

App.js

import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';

export default class App extends Component {
  state = {
    greeting: undefined
  };

  render () {
    if (this.state.greeting) return this.renderAfterButton();
    return (
      <View
        testID="welcome"
        style={{
          flex: 1,
          paddingTop: 20,
          justifyContent: 'center',
          alignItems: 'center'
        }}>
        <Text style={{ fontSize: 25, marginBottom: 30 }}>Welcome</Text>
        <TouchableOpacity
          testID="hello_button"
          onPress={this.onButtonPress.bind(this, 'Hello')}>
          <Text style={{ color: 'blue', marginBottom: 20 }}>Say Hello</Text>
        </TouchableOpacity>
        <TouchableOpacity
          testID="world_button"
          onPress={this.onButtonPress.bind(this, 'World')}>
          <Text style={{ color: 'blue', marginBottom: 20 }}>Say World</Text>
        </TouchableOpacity>
      </View>
    );
  }
  renderAfterButton () {
    return (
      <View
        style={{
          flex: 1,
          paddingTop: 20,
          justifyContent: 'center',
          alignItems: 'center'
        }}>
        <Text style={{ fontSize: 25 }}>{this.state.greeting}!!!</Text>
      </View>
    );
  }

  onButtonPress (greeting) {
    this.setState({
      greeting: greeting
    });
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center'
  }
});

passing test screen shot

Andrew
  • 18,946
  • 6
  • 54
  • 66
  • Dude, thanks so much for your help on this! I have a fully functional and passing Detox test suite now! – Chase Small Feb 26 '19 at 21:56
  • Why are you using by.label() here to test for text? The docs says by label matches for accessibilityLabels – Vgoose Mar 01 '19 at 23:57
  • I know that’s what the [docs](https://github.com/wix/Detox/blob/master/docs/APIRef.Matchers.md#bylabellabel) say. Though when I tried it with `.text` it would fail changing it to `.label` worked. It seems that there no consistency. To be fair they even say in the [docs](https://github.com/wix/Detox/blob/master/docs/APIRef.Matchers.md) that it is best to match on `id`. I’m not sure if this is an Expo issue because when I use `Detox` in a full `react-native` app I get less issues with `.text`. Try and see what works for you. – Andrew Mar 02 '19 at 06:47
  • Also is you look at the `Detox` `Expo` example [app](https://github.com/expo/with-detox-tests/) this is what their [test](https://github.com/expo/with-detox-tests/blob/master/e2e/firstTest.spec.js) looks like. Notice this use of `.label` – Andrew Mar 02 '19 at 06:59
  • Thanks for your response. I'm learning all the little quirks of this library. – Vgoose Mar 02 '19 at 12:56