Skip to content

Commit

Permalink
Basic support for Jest runner (#335)
Browse files Browse the repository at this point in the history
Add basic Jest runner support in Detox core and detox-cli
  • Loading branch information
Kureev authored and rotemmiz committed Oct 17, 2017
1 parent 983abe8 commit 2647b68
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 62 deletions.
58 changes: 43 additions & 15 deletions detox/local-cli/detox-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const program = require('commander');
const path = require('path');
const cp = require('child_process');
program
.option('-r, --runner [runner]', 'Test runner (currently supports mocha)', 'mocha')
.option('-r, --runner [runner]', 'Test runner (currently supports mocha)')
.option('-o, --runner-config [config]', 'Test runner config file', 'mocha.opts')
.option('-l, --loglevel [value]', 'info, debug, verbose, silly, wss')
.option('-c, --configuration [device configuration]', 'Select a device configuration from your defined configurations,'
Expand All @@ -21,26 +21,54 @@ program
const config = require(path.join(process.cwd(), 'package.json')).detox;
const testFolder = config.specs || 'e2e';

const loglevel = program.loglevel ? `--loglevel ${program.loglevel}` : '';
const configuration = program.configuration ? `--configuration ${program.configuration}` : '';
const cleanup = program.cleanup ? `--cleanup` : '';
const reuse = program.reuse ? `--reuse` : '';
const artifactsLocation = program.artifactsLocation ? `--artifacts-location ${program.artifactsLocation}` : '';
let runner = config.runner || 'mocha';

if (program.runner) {
runner = program.runner;
}

function runMocha() {
const loglevel = program.loglevel ? `--loglevel ${program.loglevel}` : '';
const configuration = program.configuration ? `--configuration ${program.configuration}` : '';
const cleanup = program.cleanup ? `--cleanup` : '';
const reuse = program.reuse ? `--reuse` : '';
const artifactsLocation = program.artifactsLocation ? `--artifacts-location ${program.artifactsLocation}` : '';

const debugSynchronization = program.debugSynchronization ? `--debug-synchronization ${program.debugSynchronization}` : '';
const command = `node_modules/.bin/mocha ${testFolder} --opts ${testFolder}/${program.runnerConfig} ${configuration} ${loglevel} ${cleanup} ${reuse} ${debugSynchronization} ${artifactsLocation}`;

console.log(command);
cp.execSync(command, {stdio: 'inherit'});
}

function runJest() {
const command = `node_modules/.bin/jest ${testFolder} --runInBand`;
console.log(command);
cp.execSync(command, {
stdio: 'inherit',
env: {
...process.env,
configuration: program.configuration,
loglevel: program.loglevel,
cleanup: program.cleanup,
reuse: program.reuse,
debugSynchronization: program.debugSynchronization,
artifactsLocation: program.artifactsLocation
}
});
}

if (typeof program.debugSynchronization === "boolean") {
program.debugSynchronization = 3000;
}
let debugSynchronization = program.debugSynchronization ? `--debug-synchronization ${program.debugSynchronization}` : '';


let command;
switch (program.runner) {
switch (runner) {
case 'mocha':
command = `node_modules/.bin/${program.runner} ${testFolder} --opts ${testFolder}/${program.runnerConfig} ${configuration} ${loglevel} ${cleanup} ${reuse} ${debugSynchronization} ${artifactsLocation}`;
runMocha();
break;
case 'jest':
runJest();
break;
default:
throw new Error(`${program.runner} is not supported in detox cli tools. You can still run your tests with the runner's own cli tool`);
throw new Error(`${runner} is not supported in detox cli tools. You can still run your tests with the runner's own cli tool`);
}

console.log(command);
cp.execSync(command, {stdio: 'inherit'});
7 changes: 3 additions & 4 deletions detox/src/Detox.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const DEVICE_CLASSES = {
};

class Detox {

constructor(userConfig) {
if (!userConfig) {
throw new Error(`No configuration was passed to detox, make sure you pass a config when calling 'detox.init(config)'`);
Expand All @@ -33,10 +32,10 @@ class Detox {
this.device = null;
this._currentTestNumber = 0;
const artifactsLocation = argparse.getArgValue('artifacts-location');
if(artifactsLocation !== undefined) {
if (artifactsLocation !== undefined) {
try {
this._artifactsPathsProvider = new ArtifactsPathsProvider(artifactsLocation);
} catch(ex) {
} catch (ex) {
log.warn(ex);
}
}
Expand Down Expand Up @@ -130,7 +129,7 @@ class Detox {
}

if (!deviceConfig) {
throw new Error(`Cannot determine which configuration to use. use --configuration to choose one of the following:
throw new Error(`Cannot determine which configuration to use. use --configuration to choose one of the following:
${Object.keys(configurations)}`);
}

Expand Down
47 changes: 22 additions & 25 deletions detox/src/Detox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ describe('Detox', () => {
let fs;
let Detox;
let detox;
let minimist;
let clientMockData = {lastConstructorArguments: null};
let deviceMockData = {lastConstructorArguments: null};
const clientMockData = {lastConstructorArguments: null};
const deviceMockData = {lastConstructorArguments: null};

beforeEach(async () => {
function setCustomMock(modulePath, dataObject) {
const JestMock = jest.genMockFromModule(modulePath);
class FinalMock extends JestMock {
constructor() {
super(...arguments);
dataObject.lastConstructorArguments = arguments;
constructor(...rest) {
super(rest);
dataObject.lastConstructorArguments = rest;
}
}
jest.setMock(modulePath, FinalMock);
Expand All @@ -23,12 +22,12 @@ describe('Detox', () => {
jest.mock('npmlog');
jest.mock('fs');
fs = require('fs');
jest.mock('minimist');
minimist = require('minimist');
jest.mock('./ios/expect');
setCustomMock('./client/Client', clientMockData);
setCustomMock('./devices/Device', deviceMockData);

process.env = {};

jest.mock('./devices/IosDriver');
jest.mock('./devices/SimulatorDriver');
jest.mock('./devices/Device');
Expand Down Expand Up @@ -66,7 +65,7 @@ describe('Detox', () => {
});

it(`Passing --cleanup should shutdown the currently running device`, async () => {
mockCommandLineArgs({cleanup: true});
process.env.cleanup = true;
Detox = require('./Detox');

detox = new Detox(schemes.validOneDeviceNoSession);
Expand Down Expand Up @@ -99,7 +98,7 @@ describe('Detox', () => {
});

it(`Two valid devices, detox should init with the device passed in '--configuration' cli option`, async () => {
mockCommandLineArgs({configuration: 'ios.sim.debug'});
process.env.configuration = 'ios.sim.debug';
Detox = require('./Detox');

detox = new Detox(schemes.validTwoDevicesNoSession);
Expand All @@ -108,7 +107,7 @@ describe('Detox', () => {
});

it(`Two valid devices, detox should throw if device passed in '--configuration' cli option doesn't exist`, async () => {
mockCommandLineArgs({configuration: 'nonexistent'});
process.env.configuration = 'nonexistent';
Detox = require('./Detox');

detox = new Detox(schemes.validTwoDevicesNoSession);
Expand All @@ -121,7 +120,7 @@ describe('Detox', () => {
});

it(`Two valid devices, detox should throw if device passed in '--configuration' cli option doesn't exist`, async () => {
mockCommandLineArgs({configuration: 'nonexistent'});
process.env.configuration = 'nonexistent';
Detox = require('./Detox');

detox = new Detox(schemes.validTwoDevicesNoSession);
Expand Down Expand Up @@ -165,22 +164,22 @@ describe('Detox', () => {
});

it(`Detox should use session defined per configuration - none`, async () => {
mockCommandLineArgs({configuration: 'ios.sim.none'});
process.env.configuration = 'ios.sim.none';
Detox = require('./Detox');
detox = new Detox(schemes.sessionPerConfiguration);
await detox.init();

let expectedSession = schemes.sessionPerConfiguration.configurations['ios.sim.none'].session;
const expectedSession = schemes.sessionPerConfiguration.configurations['ios.sim.none'].session;
expect(clientMockData.lastConstructorArguments[0]).toBe(expectedSession);
});

it(`Detox should use session defined per configuration - release`, async () => {
mockCommandLineArgs({configuration: 'ios.sim.release'});
process.env.configuration = 'ios.sim.release';
Detox = require('./Detox');
detox = new Detox(schemes.sessionPerConfiguration);
await detox.init();

let expectedSession = schemes.sessionPerConfiguration.configurations['ios.sim.release'].session;
const expectedSession = schemes.sessionPerConfiguration.configurations['ios.sim.release'].session;
expect(clientMockData.lastConstructorArguments[0]).toBe(expectedSession);
});

Expand All @@ -189,12 +188,12 @@ describe('Detox', () => {
detox = new Detox(schemes.sessionInCommonAndInConfiguration);
await detox.init();

let expectedSession = schemes.sessionInCommonAndInConfiguration.configurations['ios.sim.none'].session;
const expectedSession = schemes.sessionInCommonAndInConfiguration.configurations['ios.sim.none'].session;
expect(clientMockData.lastConstructorArguments[0]).toBe(expectedSession);
});

it(`beforeEach() - should set device artifacts destination`, async () => {
mockCommandLineArgs({'artifacts-location': '/tmp'});
process.env.artifactsLocation = '/tmp';
Detox = require('./Detox');
detox = new Detox(schemes.validOneDeviceAndSession);
await detox.init();
Expand All @@ -211,7 +210,7 @@ describe('Detox', () => {
});

it(`afterEach() - should call device.finalizeArtifacts`, async () => {
mockCommandLineArgs({'artifacts-location': '/tmp'});
process.env.artifactsLocation = '/tmp';
Detox = require('./Detox');
detox = new Detox(schemes.validOneDeviceAndSession);
await detox.init();
Expand All @@ -228,13 +227,11 @@ describe('Detox', () => {
});

it(`the constructor should catch exception from ArtifactsPathsProvider`, async () => {
mockCommandLineArgs({'artifacts-location': '/tmp'});
fs.mkdirSync = jest.fn(() => {throw 'Could not create artifacts root dir'});
process.env.artifactsLocation = '/tmp';
fs.mkdirSync = jest.fn(() => {
throw Error('Could not create artifacts root dir');
});
Detox = require('./Detox');
detox = new Detox(schemes.validOneDeviceAndSession);
});

function mockCommandLineArgs(args) {
minimist.mockReturnValue(args);
}
});
10 changes: 9 additions & 1 deletion detox/src/utils/argparse.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
const argv = require('minimist')(process.argv.slice(2));

function getArgValue(key) {
const value = argv ? argv[key] : undefined;
let value;

if (argv && argv[key]) {
value = argv[key];
} else {
const camelCasedKey = key.replace(/(\-\w)/g, (m) => m[1].toUpperCase());
value = process.env[camelCasedKey];
}

return value;
}

Expand Down
43 changes: 31 additions & 12 deletions detox/src/utils/argparse.test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
const _ = require('lodash');
jest.unmock('process');

describe('argparse', () => {
let argparse;
describe('using env variables', () => {
let argparse;

beforeEach(() => {
jest.mock('minimist');
const minimist = require('minimist');
minimist.mockReturnValue({test: 'a value'});
argparse = require('./argparse');
});
beforeEach(() => {
process.env.fooBar = 'a value';
argparse = require('./argparse');
});

it(`nonexistent key should return undefined result`, () => {
expect(argparse.getArgValue('blah')).not.toBeDefined();
});

it(`nonexistent key should return undefined result`, () => {
expect(argparse.getArgValue('blah')).not.toBeDefined();
it(`existing key should return a result`, () => {
expect(argparse.getArgValue('foo-bar')).toBe('a value');
});
});

it(`existing key should return a result`, () => {
expect(argparse.getArgValue('test')).toBe('a value');
describe('using arguments', () => {
let argparse;

beforeEach(() => {
jest.mock('minimist');
const minimist = require('minimist');
minimist.mockReturnValue({'kebab-case-key': 'a value'});
argparse = require('./argparse');
});

it(`nonexistent key should return undefined result`, () => {
expect(argparse.getArgValue('blah')).not.toBeDefined();
});

it(`existing key should return a result`, () => {
expect(argparse.getArgValue('kebab-case-key')).toBe('a value');
});
});
});
9 changes: 4 additions & 5 deletions docs/Guide.Jest.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ npm install --save-dev jest

You should remove `e2e/mocha.opts`, you no longer need it.

### 3. Write a detox setup file
### 3. Replace generated detox setup file (e2e/init.js)

```js
// ./jest/setup/e2e.js
const detox = require('detox');
const config = require('../package.json').detox;

Expand All @@ -44,15 +43,15 @@ beforeEach(async () => {
Add this part to your `package.json`:
```json
"jest": {
"setupTestFrameworkScriptFile": "<rootDir>/jest/setup.js"
"setupTestFrameworkScriptFile": "./e2e/init.js"
},
"scripts": {
"test:e2e": "jest __e2e__ --setupTestFrameworkScriptFile=./jest/setup/e2e-tests.js --runInBand",
"test:e2e": "detox test",
"test:e2e:build": "detox build"
}
```

We need the `--runInBand` as detox doesn't support parallelism yet.
In the `detox` part of your `package.json`, add `"runner": "jest"` to tell detox that you want to use jest runner instead of mocha.

### Writing Tests

Expand Down

0 comments on commit 2647b68

Please sign in to comment.