Is it actually possible to make Detox/Jest tests p

2019-07-08 08:42发布

问题:

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();
    });
  });

回答1:

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

  • Detox's own Expo Guide
  • Detox setup
  • Expo sample app with Detox

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'
  }
});