chore: test framework (#1894)

* chore: test framework

* chore(@embark/testing): introduce Plugin apis and other changes

* refactor(@embark/deployment): use new testing APIs for tests
This commit is contained in:
André Medeiros 2019-09-12 17:26:57 -04:00 committed by Iuri Matias
parent 009960e51a
commit 2f9d5e6085
15 changed files with 357 additions and 98 deletions

View File

@ -0,0 +1,4 @@
engine-strict = true
package-lock = false
save-exact = true
scripts-prepend-node-path = true

View File

@ -0,0 +1,7 @@
testing
=======
> Testing
Visit [embark.status.im](https://embark.status.im/) to get started with
[Embark](https://github.com/embark-framework/embark).

View File

@ -0,0 +1,42 @@
const cloneDeep = require('lodash.clonedeep');
module.exports = api => {
const env = api.env();
const base = {
plugins: [
'@babel/plugin-proposal-class-properties',
[
'@babel/plugin-transform-runtime',
{
corejs: 2
}
]
],
presets: ['@babel/preset-env']
};
if (env === 'base' || env.startsWith('base:')) {
return base;
}
const node = cloneDeep(base);
node.presets[0] = [
node.presets[0],
{
targets: { node: '8.11.3' }
}
];
if (env === 'node' || env.startsWith('node:')) {
return node;
}
const test = cloneDeep(node);
if (env === 'test') {
return test;
}
return {};
};

View File

@ -0,0 +1,73 @@
{
"name": "embark-testing",
"version": "5.0.0",
"description": "Testing",
"main": "dist/lib/index.js",
"repository": {
"directory": "packages/testing",
"type": "git",
"url": "https://github.com/embark-framework/embark/"
},
"author": "Iuri Matias <iuri.matias@gmail.com>",
"license": "MIT",
"bugs": "https://github.com/embark-framework/embark/issues",
"keywords": [
"blockchain",
"dapps",
"ethereum",
"ipfs",
"serverless",
"solc",
"solidity"
],
"files": [
"dist"
],
"scripts": {
"build": "cross-env BABEL_ENV=node babel src --extensions \".js\" --out-dir dist --root-mode upward --source-maps",
"ci": "npm run qa",
"clean": "npm run reset",
"lint": "npm-run-all lint:*",
"lint:js": "eslint src/",
"// lint:ts": "tslint -c tslint.json \"src/**/*.ts\"",
"package": "npm pack",
"// qa": "npm-run-all lint typecheck build package",
"qa": "npm-run-all lint build package",
"reset": "npx rimraf dist embark-*.tgz package",
"start": "npm run watch",
"// typecheck": "tsc",
"watch": "run-p watch:*",
"watch:build": "npm run build -- --verbose --watch",
"// watch:typecheck": "npm run typecheck -- --preserveWatchOutput --watch",
"test": "jest dist/test/**/*.js"
},
"eslintConfig": {
"extends": "../../.eslintrc.json"
},
"dependencies": {
"refute": "1.0.2",
"sinon": "7.4.2"
},
"devDependencies": {
"@babel/cli": "7.2.3",
"@babel/core": "7.2.2",
"@babel/plugin-proposal-class-properties": "7.5.5",
"@babel/plugin-transform-runtime": "7.5.5",
"@babel/preset-env": "7.5.5",
"@types/async": "2.0.50",
"babel-jest": "24.9.0",
"cross-env": "5.2.0",
"eslint": "5.7.0",
"jest": "24.9.0",
"lodash.clonedeep": "4.5.0",
"npm-run-all": "4.1.5",
"rimraf": "2.6.3",
"tslint": "5.16.0",
"typescript": "3.4.5"
},
"engines": {
"node": ">=8.12.0 <12.0.0",
"npm": ">=6.4.1",
"yarn": ">=1.12.3"
}
}

View File

@ -0,0 +1,40 @@
const sinon = require('sinon');
class Embark {
constructor(events, plugins) {
this.events = events;
this.plugins = plugins;
this.config = {
blockchainConfig: {}
};
this.assert = new EmbarkAssert(this);
this.logger = {
debug: sinon.fake(),
info: sinon.fake(),
warn: sinon.fake(),
error: sinon.fake(),
};
}
registerActionForEvent(name, cb) {
this.plugins.registerActionForEvent(name, cb);
}
teardown() {
this.plugins.teardown();
}
}
class EmbarkAssert {
constructor(embark) {
this.embark = embark;
}
logged(level, message) {
sinon.assert.calledWithMatch(this.embark.logger[level], message);
}
}
module.exports = Embark;

View File

@ -0,0 +1,68 @@
const assert = require('assert');
const sinon = require('sinon');
class Events {
constructor() {
this.commandHandlers = {};
this.handlers = {};
this.assert = new EventsAssert(this);
}
setCommandHandler(cmd, fn) {
this.commandHandlers[cmd] = sinon.spy(fn);
}
on(ev, cb) {
if (!this.handlers[ev]) {
this.handlers[ev] = [];
}
this.handlers[ev].push(cb);
}
emit() {
}
trigger(ev, ...args) {
if (!this.handlers[ev]) {
return;
}
this.handlers[ev].forEach(h => h(...args));
}
request(cmd, ...args) {
assert(this.commandHandlers[cmd], `command handler for ${ cmd } not registered`);
Promise.resolve(this.commandHandlers[cmd](...args));
}
request2(cmd, ...args) {
assert(this.commandHandlers[cmd], `command handler for ${ cmd } not registered`);
this.commandHandlers[cmd](...args);
}
}
class EventsAssert {
constructor(events) {
this.events = events;
}
commandHandlerRegistered(cmd) {
assert(this.events.commandHandlers[cmd], `command handler for ${ cmd } wanted, but not registered`);
}
commandHandlerCalled(cmd) {
this.commandHandlerRegistered(cmd);
sinon.assert.called(this.events.commandHandlers[cmd]);
}
commandHandlerCalledWith(cmd, ...args) {
this.commandHandlerRegistered(cmd);
sinon.assert.calledWith(this.events.commandHandlers[cmd], ...args);
}
}
module.exports = Events;

View File

@ -0,0 +1,22 @@
const Embark = require('./embark');
const Events = require('./events');
const Plugins = require('./plugin');
const fakeEmbark = () => {
const events = new Events();
const plugins = new Plugins();
const embark = new Embark(events, plugins);
return {
embark,
plugins
};
};
module.exports = {
Embark,
Events,
Plugins,
fakeEmbark
};

View File

@ -0,0 +1,44 @@
class Plugins {
constructor() {
this.plugin = new Plugin();
}
createPlugin() {
return this.plugin;
}
emitAndRunActionsForEvent(name, options, callback) {
const listeners = this.plugin.getListeners(name);
if (listeners) {
listeners.forEach(fn => fn(options, callback));
}
}
registerActionForEvent(name, cb) {
this.plugin.registerActionForEvent(name, cb);
}
teardown() {
this.plugin.listeners = {};
}
}
class Plugin {
constructor() {
this.listeners = {};
}
getListeners(name) {
return this.listeners[name];
}
registerActionForEvent(name, action) {
if (!this.listeners[name]) {
this.listeners[name] = [];
}
this.listeners[name].push(action);
}
}
module.exports = Plugins;

View File

@ -0,0 +1,10 @@
/* globals describe, it */
const assert = require('assert').strict;
describe('Testing', () => {
it('should have tests', (done) => {
assert(false, 'No tests yet on testing');
done();
});
});

View File

@ -0,0 +1,3 @@
{
"extends": "../../../tslint.json"
}

View File

@ -62,6 +62,7 @@
"@babel/preset-env": "7.5.5",
"babel-jest": "24.9.0",
"cross-env": "5.2.0",
"embark-testing": "^5.0.0",
"eslint": "5.7.0",
"jest": "24.9.0",
"npm-run-all": "4.1.5",

View File

@ -1,69 +1,12 @@
import sinon from 'sinon';
import assert from 'assert';
import { fakeEmbark, Plugins } from 'embark-testing';
import Deployment from '../src/';
const events = {
listeners: {},
setCommandHandler: sinon.spy((cmd, fn) => {
events.listeners[cmd] = fn;
}),
request: sinon.spy((cmd, ...args) => {
assert(events.listeners[cmd]);
events.listeners[cmd](...args);
}),
emit: (name) => {}
};
const plugins = {
listeners: {},
_plugin: {
listeners: {},
registerActionForEvent: (name, cb) => {
if (!plugins._plugin.listeners[name]) {
plugins._plugin.listeners[name] = [];
}
plugins._plugin.listeners[name].push(cb);
}
},
createPlugin: (name, opts) => {
return _plugin;
},
emitAndRunActionsForEvent: sinon.spy((name, options, cb) => {
if (plugins._plugin.listeners[name]) {
plugins._plugin.listeners[name].forEach(fn => {
fn(options, cb);
});
}
})
};
const fakeEmbark = {
events,
logger: {
info: () => {}
},
config: {
blockchainConfig: {}
},
assert: {
hasCommandHandler: (cmd) => {
sinon.assert.calledWith(fakeEmbark.events.setCommandHandler, cmd);
},
hasRequested: (cmd) => {
sinon.assert.calledWith(fakeEmbark.events.request, cmd);
},
hasRequestedWith: (cmd, ...args) => {
sinon.assert.calledWith(fakeEmbark.events.request, cmd, ...args);
},
},
teardown: () => {
fakeEmbark.events.listeners = {};
plugins._plugin.listeners = {};
}
};
describe('stack/deployment', () => {
const { embark, plugins } = fakeEmbark();
let deployment;
let deployedContracts = [];
@ -72,7 +15,7 @@ describe('stack/deployment', () => {
let doneCb;
beforeEach(() => {
deployment = new Deployment(fakeEmbark, { plugins });
deployment = new Deployment(embark, { plugins });
beforeAllAction = sinon.spy((params, cb) => { cb(null, params); });
beforeDeployAction = sinon.spy((params, cb) => { cb(null, params); });
@ -90,7 +33,7 @@ describe('stack/deployment', () => {
afterEach(() => {
deployedContracts = [];
fakeEmbark.teardown();
embark.teardown();
sinon.restore();
});
@ -98,12 +41,12 @@ describe('stack/deployment', () => {
let testContract = { className: 'TestContract', shouldDeploy: true };
plugins._plugin.registerActionForEvent('deployment:contract:beforeDeploy', beforeDeployAction);
plugins._plugin.registerActionForEvent('deployment:contract:shouldDeploy', shouldDeployAction);
plugins._plugin.registerActionForEvent('deployment:contract:deployed', deployedAction);
embark.registerActionForEvent('deployment:contract:beforeDeploy', beforeDeployAction);
embark.registerActionForEvent('deployment:contract:shouldDeploy', shouldDeployAction);
embark.registerActionForEvent('deployment:contract:deployed', deployedAction);
events.request('deployment:deployer:register', 'ethereum', deployFn);
events.request('deployment:contract:deploy', testContract, doneCb);
embark.events.request('deployment:deployer:register', 'ethereum', deployFn);
embark.events.request('deployment:contract:deploy', testContract, doneCb);
assert(beforeDeployAction.calledOnce)
assert(shouldDeployAction.calledOnce)
@ -120,20 +63,20 @@ describe('stack/deployment', () => {
{ className: 'Contract3', shouldDeploy: true }
];
plugins._plugin.registerActionForEvent('deployment:deployContracts:beforeAll', beforeAllAction);
plugins._plugin.registerActionForEvent('deployment:contract:beforeDeploy', beforeDeployAction);
plugins._plugin.registerActionForEvent('deployment:contract:shouldDeploy', shouldDeployAction);
plugins._plugin.registerActionForEvent('deployment:contract:deployed', deployedAction);
plugins._plugin.registerActionForEvent('deployment:deployContracts:afterAll', afterAllAction);
embark.registerActionForEvent('deployment:deployContracts:beforeAll', beforeAllAction);
embark.registerActionForEvent('deployment:contract:beforeDeploy', beforeDeployAction);
embark.registerActionForEvent('deployment:contract:shouldDeploy', shouldDeployAction);
embark.registerActionForEvent('deployment:contract:deployed', deployedAction);
embark.registerActionForEvent('deployment:deployContracts:afterAll', afterAllAction);
events.request('deployment:deployer:register', 'ethereum', deployFn);
events.request('deployment:contracts:deploy', testContracts, {}, doneCb);
embark.events.request('deployment:deployer:register', 'ethereum', deployFn);
embark.events.request('deployment:contracts:deploy', testContracts, {}, doneCb);
assert(beforeAllAction.calledOnce);
fakeEmbark.assert.hasRequestedWith('deployment:contract:deploy', testContracts[0]);
fakeEmbark.assert.hasRequestedWith('deployment:contract:deploy', testContracts[1]);
fakeEmbark.assert.hasRequestedWith('deployment:contract:deploy', testContracts[2]);
embark.events.assert.commandHandlerCalledWith('deployment:contract:deploy', testContracts[0]);
embark.events.assert.commandHandlerCalledWith('deployment:contract:deploy', testContracts[1]);
embark.events.assert.commandHandlerCalledWith('deployment:contract:deploy', testContracts[2]);
assert(deployFn.calledWith(testContracts[0]));
assert(deployFn.calledWith(testContracts[1]));
@ -164,14 +107,14 @@ describe('stack/deployment', () => {
F: []
};
plugins._plugin.registerActionForEvent('deployment:deployContracts:beforeAll', beforeAllAction);
plugins._plugin.registerActionForEvent('deployment:contract:beforeDeploy', beforeDeployAction);
plugins._plugin.registerActionForEvent('deployment:contract:shouldDeploy', shouldDeployAction);
plugins._plugin.registerActionForEvent('deployment:contract:deployed', deployedAction);
plugins._plugin.registerActionForEvent('deployment:deployContracts:afterAll', afterAllAction);
embark.registerActionForEvent('deployment:deployContracts:beforeAll', beforeAllAction);
embark.registerActionForEvent('deployment:contract:beforeDeploy', beforeDeployAction);
embark.registerActionForEvent('deployment:contract:shouldDeploy', shouldDeployAction);
embark.registerActionForEvent('deployment:contract:deployed', deployedAction);
embark.registerActionForEvent('deployment:deployContracts:afterAll', afterAllAction);
events.request('deployment:deployer:register', 'ethereum', deployFn);
events.request('deployment:contracts:deploy', testContracts, testContractDependencies, doneCb);
embark.events.request('deployment:deployer:register', 'ethereum', deployFn);
embark.events.request('deployment:contracts:deploy', testContracts, testContractDependencies, doneCb);
assert.equal(deployedContracts.length, 6);

View File

@ -54,6 +54,7 @@
"async": "2.6.1",
"chalk": "2.4.2",
"embark-i18n": "^4.1.1",
"embark-testing": "5.0.0",
"embark-utils": "^4.1.1",
"fs-extra": "7.0.1",
"istanbul-lib-coverage": "2.0.5",

View File

@ -2,17 +2,17 @@ const assert = require('assert').strict;
const refute = require('refute')(assert);
const sinon = require('sinon');
const {fakeEmbark} = require('embark-testing');
const TestRunner = require('../lib/index.js');
describe('Test Runner', () => {
let embark;
let _embark;
let instance;
beforeEach(() => {
const events = { setCommandHandler: () => {}, on: () => {} };
const logger = { warn: sinon.fake() };
embark = { events, logger };
const { embark } = fakeEmbark();
_embark = embark;
instance = new TestRunner(embark, {});
});
@ -36,6 +36,7 @@ describe('Test Runner', () => {
};
instance.runners = [first, second];
instance.getFilesFromDir = (_, cb) => {
cb(null, ['test/file_first.js', 'test/file_second.js']);
};
@ -54,7 +55,7 @@ describe('Test Runner', () => {
sinon.assert.calledWith(second.matchFn, 'luri.js');
// Ensure that we logged
sinon.assert.calledWithMatch(embark.logger.warn, /luri.js/);
_embark.assert.logged('warn', /luri.js/);
done();
});
@ -66,7 +67,7 @@ describe('Test Runner', () => {
refute(err);
// Ensure that we didn't warn that runners weren't registered.
sinon.assert.notCalled(embark.logger.warn);
sinon.assert.notCalled(_embark.logger.warn);
// Ensure plugins received the correct files
sinon.assert.calledWith(first.addFn, 'test/file_first.js');

View File

@ -18415,6 +18415,13 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.
dependencies:
glob "^7.1.3"
rimraf@2.6.3, rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
dependencies:
glob "^7.1.3"
rimraf@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b"
@ -18422,13 +18429,6 @@ rimraf@3.0.0:
dependencies:
glob "^7.1.3"
rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
dependencies:
glob "^7.1.3"
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"