diff --git a/lib/app_handler.js b/lib/app_handler.js index 7a7218f..50fa84f 100644 --- a/lib/app_handler.js +++ b/lib/app_handler.js @@ -43,9 +43,9 @@ exports.setGetAppCallback = cb => { getAppCallback = cb; }; -exports.getApp = async suite => { +exports.getApp = async (suite, test) => { if (getAppCallback) { - return getAppCallback(suite); + return getAppCallback(suite, test); } if (app) { await app.ready(); diff --git a/lib/inject_context.js b/lib/inject_context.js index 2972bb5..bf06716 100644 --- a/lib/inject_context.js +++ b/lib/inject_context.js @@ -1,6 +1,5 @@ const debug = require('util').debuglog('egg-mock:inject_context'); const appHandler = require('./app_handler'); -const EGG_CONTEXT = Symbol.for('mocha#suite#ctx#eggCtx'); /** * Monkey patch the mocha instance with egg context. @@ -16,11 +15,26 @@ function injectContext(mocha) { const runSuite = Runner.prototype.runSuite; const runTests = Runner.prototype.runTests; + function getTestTitle(suite, test) { + const suiteTitle = suite.root ? 'root suite' : suite.title; + if (!test) { + return `"${suiteTitle}"`; + } + return `"${suiteTitle} - ${test.title}"`; + } + // Inject ctx for before/after. Runner.prototype.runSuite = async function(suite, fn) { - debug('run suite: %j %s', suite.title, !!(suite.ctx && suite[EGG_CONTEXT])); - const app = await appHandler.getApp(suite); - debug('get app: %s', !!app); + debug('run suite: %s', suite.title); + let app; + try { + app = await appHandler.getApp(suite); + debug('get app: %s', !!app); + } catch (err) { + err.message = `[egg-mock/runSuite] get app for ${getTestTitle(suite)}: ${err.message}`; + this.fail(suite, err); + return fn(suite); + } const self = this; if (!app) { return runSuite.call(self, suite, fn); @@ -28,9 +42,9 @@ function injectContext(mocha) { let errSuite; try { await app.ready(); + suite.ctx.app = app; const mockContextFun = app.mockModuleContextScope || app.mockContextScope; - await mockContextFun.call(app, async function(eggCtx) { - suite.ctx[EGG_CONTEXT] = eggCtx; + await mockContextFun.call(app, async function() { await new Promise(resolve => { runSuite.call(self, suite, aErrSuite => { errSuite = aErrSuite; @@ -38,9 +52,9 @@ function injectContext(mocha) { }); }); }); - } catch (e) { - e.message = '[egg-mock] inject context error: ' + e.message; - console.error(e); + } catch (err) { + err.message = `[egg-mock/runSuite] inject context for ${getTestTitle(suite)}: ${err.message}`; + self.fail(suite, err); } return fn(errSuite); }; @@ -48,15 +62,18 @@ function injectContext(mocha) { // Inject ctx for beforeEach/it/afterEach. // And ctx with before/after is not same as beforeEach/it/afterEach. Runner.prototype.runTests = async function(suite, fn) { - debug('run tests: %j %s', suite.title); - const app = await appHandler.getApp(suite); + const tests = suite.tests.slice(); + if (!tests.length) { + return runTests.call(this, suite, fn); + } + + const app = suite.ctx.app; + const self = this; if (!app) { return runTests.call(self, suite, fn); } - const tests = suite.tests.slice(); - function done(errSuite) { suite.tests = tests; return fn(errSuite); @@ -69,34 +86,35 @@ function injectContext(mocha) { } suite.tests = [ test ]; - const app = await appHandler.getApp(suite); - await app.ready(); - const mockContextFun = app.mockModuleContextScope || app.mockContextScope; - let testPassed = false; + let app; try { - testPassed = await mockContextFun.call(app, async function() { + app = await appHandler.getApp(suite, test); + await app.ready(); + } catch (err) { + err.message = `[egg-mock/runTests] get app for ${getTestTitle(suite, test)}: ${err.message}`; + self.fail(test, err); + return next(i + 1); + } + + try { + const mockContextFun = app.mockModuleContextScope || app.mockContextScope; + await mockContextFun.call(app, async function() { return await new Promise(resolve => { - runTests.call(self, suite, err => { - if (err) { - return resolve(false); - } - return resolve(true); + runTests.call(self, suite, () => { + return resolve(); }); }); }); } catch (err) { - err.message = '[egg-mock] run mock context error: ' + err.message; - console.error(err); - return done(suite); - } - if (testPassed) { + err.message = `[egg-mock/runTests] create context for ${getTestTitle(suite)} error: ${err.message}`; + self.fail(test, err); return next(i + 1); } - return done(suite); + return next(i + 1); } - next(0).catch(e => { - e.message = '[egg-mock] inject context error: ' + e.message; - console.error(e); + next(0).catch(err => { + err.message = `[egg-mock/runTests] unknown error ${getTestTitle(suite)} error: ${err.message}`; + self.fail(suite, err); done(suite); }); }; diff --git a/test/fixtures/create-context-failed/config/config.default.js b/test/fixtures/create-context-failed/config/config.default.js new file mode 100644 index 0000000..13f1f27 --- /dev/null +++ b/test/fixtures/create-context-failed/config/config.default.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + keys: '123', +}; diff --git a/test/fixtures/create-context-failed/package.json b/test/fixtures/create-context-failed/package.json new file mode 100644 index 0000000..fd1f8f6 --- /dev/null +++ b/test/fixtures/create-context-failed/package.json @@ -0,0 +1,3 @@ +{ + "name": "app" +} diff --git a/test/fixtures/create-context-failed/test/index.test.js b/test/fixtures/create-context-failed/test/index.test.js new file mode 100644 index 0000000..eda2533 --- /dev/null +++ b/test/fixtures/create-context-failed/test/index.test.js @@ -0,0 +1,17 @@ +const { setGetAppCallback } = require('../../../..'); + +setGetAppCallback(() => { + return { + ready: async () => { + // ... + }, + mockContextScope: async () => { + throw new Error('mock create context failed'); + }, + }; +}); + +describe('test case create context error', () => { + it('should not print', () => { + }); +}); diff --git a/test/fixtures/failed-app/test/index.test.js b/test/fixtures/failed-app/test/index.test.js index 846b145..facde83 100644 --- a/test/fixtures/failed-app/test/index.test.js +++ b/test/fixtures/failed-app/test/index.test.js @@ -1,32 +1,53 @@ const assert = require('assert'); -const { app } = require('../../../../bootstrap'); describe('test/index.test.ts', () => { - describe('hook error', () => { + describe('before error', () => { + before(() => { + throw new Error('before error'); + }); + + it('should not print', () => { + assert.fail('never arrive'); + }); + }); + + describe('after error', () => { + after(() => { + throw new Error('after error'); + }); + + it('should print', () => { + console.log('after error test case should print'); + }); + }); + + describe('beforeEach error', () => { beforeEach(() => { - assert.fail('failed hook'); + throw new Error('beforeEach error'); }); - it('should fail', () => { - // eslint-disable-next-line no-undef - assert(app.currentContext); - assert.fail('failed case'); + it('should not print', () => { + assert.fail('never arrive'); }); }); - describe('mockContext failed', () => { - before(() => { - app.mockContextScope = () => { - throw new Error('mockContextScope error'); - }; + describe('afterEach error', () => { + afterEach(() => { + throw new Error('afterEach error'); }); - it('foo', () => { - //... + it('should print', () => { + console.log('afterEach error test case should print'); + }); + }); + + describe('case error', () => { + it('should failed', () => { + assert.fail('should fail'); }); - it('should not run', () => { - console.log('it should not run'); + it('should work', () => { + // ... }); }); }); diff --git a/test/fixtures/get-app-failed/config/config.default.js b/test/fixtures/get-app-failed/config/config.default.js new file mode 100644 index 0000000..13f1f27 --- /dev/null +++ b/test/fixtures/get-app-failed/config/config.default.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + keys: '123', +}; diff --git a/test/fixtures/get-app-failed/package.json b/test/fixtures/get-app-failed/package.json new file mode 100644 index 0000000..fd1f8f6 --- /dev/null +++ b/test/fixtures/get-app-failed/package.json @@ -0,0 +1,3 @@ +{ + "name": "app" +} diff --git a/test/fixtures/get-app-failed/test/index.test.js b/test/fixtures/get-app-failed/test/index.test.js new file mode 100644 index 0000000..fa45361 --- /dev/null +++ b/test/fixtures/get-app-failed/test/index.test.js @@ -0,0 +1,10 @@ +const { setGetAppCallback } = require('../../../..'); + +setGetAppCallback(() => { + throw new Error('mock get app failed'); +}); + +describe('test case create context error', () => { + it('should not print', () => { + }); +}); diff --git a/test/fixtures/test-case-create-context-failed/config/config.default.js b/test/fixtures/test-case-create-context-failed/config/config.default.js new file mode 100644 index 0000000..13f1f27 --- /dev/null +++ b/test/fixtures/test-case-create-context-failed/config/config.default.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + keys: '123', +}; diff --git a/test/fixtures/test-case-create-context-failed/package.json b/test/fixtures/test-case-create-context-failed/package.json new file mode 100644 index 0000000..fd1f8f6 --- /dev/null +++ b/test/fixtures/test-case-create-context-failed/package.json @@ -0,0 +1,3 @@ +{ + "name": "app" +} diff --git a/test/fixtures/test-case-create-context-failed/test/index.test.js b/test/fixtures/test-case-create-context-failed/test/index.test.js new file mode 100644 index 0000000..76bb359 --- /dev/null +++ b/test/fixtures/test-case-create-context-failed/test/index.test.js @@ -0,0 +1,21 @@ +const { setGetAppCallback } = require('../../../..'); + +setGetAppCallback((suite, test) => { + return { + ready: async () => { + // ... + }, + mockContextScope: async scope => { + if (!test) { + await scope({}); + } else { + throw new Error('mock create context failed'); + } + }, + }; +}); + +describe('test case create context error', function() { + it('should not print', () => { + }); +}); diff --git a/test/fixtures/test-case-get-app-failed/config/config.default.js b/test/fixtures/test-case-get-app-failed/config/config.default.js new file mode 100644 index 0000000..13f1f27 --- /dev/null +++ b/test/fixtures/test-case-get-app-failed/config/config.default.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + keys: '123', +}; diff --git a/test/fixtures/test-case-get-app-failed/package.json b/test/fixtures/test-case-get-app-failed/package.json new file mode 100644 index 0000000..fd1f8f6 --- /dev/null +++ b/test/fixtures/test-case-get-app-failed/package.json @@ -0,0 +1,3 @@ +{ + "name": "app" +} diff --git a/test/fixtures/test-case-get-app-failed/test/index.test.js b/test/fixtures/test-case-get-app-failed/test/index.test.js new file mode 100644 index 0000000..d459f92 --- /dev/null +++ b/test/fixtures/test-case-get-app-failed/test/index.test.js @@ -0,0 +1,20 @@ +const { setGetAppCallback } = require('../../../..'); + +setGetAppCallback((suite, test) => { + if (test) { + throw new Error('mock get app failed'); + } + return { + ready: async () => { + // ... + }, + mockContextScope: async scope => { + await scope({}); + }, + }; +}); + +describe('test case get app error', () => { + it('should not print', () => { + }); +}); diff --git a/test/inject_ctx.test.js b/test/inject_ctx.test.js index d3c5341..c4a339a 100644 --- a/test/inject_ctx.test.js +++ b/test/inject_ctx.test.js @@ -37,7 +37,7 @@ describe('test/inject_ctx.test.js', () => { .end(); }); - it('failed case', async () => { + it('hook/case error should failed', async () => { const fixture = path.join(__dirname, 'fixtures/failed-app'); await coffee.fork(require.resolve('egg-bin/bin/egg-bin'), [ @@ -51,8 +51,96 @@ describe('test/inject_ctx.test.js', () => { }, }) // .debug() + .expect('stdout', /after error test case should print/) + .expect('stdout', /afterEach error test case should print/) + .expect('stdout', /"before all" hook for "should not print"/) + .expect('stdout', /"after all" hook for "should print"/) + .expect('stdout', /"before each" hook for "should not print"/) + .expect('stdout', /"after each" hook for "should print"/) + .expect('stdout', /3 passing/) + // 1 after + 1 afterEach + 1 before + 1 beforeEach + 1 test case + .expect('stdout', /5 failing/) .expect('code', 1) - .expect('stderr', /run mock context error: mockContextScope error/) .end(); }); + + describe('run suite', () => { + it('get app error should failed', async () => { + const fixture = path.join(__dirname, 'fixtures/get-app-failed'); + + await coffee.fork(require.resolve('egg-bin/bin/egg-bin'), [ + 'test', + '-r', require.resolve('../register'), + '--full-trace', + ], { + cwd: fixture, + env: { + EGG_FRAMEWORK: require.resolve('egg'), + }, + }) + // .debug() + .expect('code', 1) + .expect('stdout', /get app for "root suite": mock get app failed/) + .end(); + }); + + it('create context error should failed', async () => { + const fixture = path.join(__dirname, 'fixtures/create-context-failed'); + + await coffee.fork(require.resolve('egg-bin/bin/egg-bin'), [ + 'test', + '-r', require.resolve('../register'), + '--full-trace', + ], { + cwd: fixture, + env: { + EGG_FRAMEWORK: require.resolve('egg'), + }, + }) + // .debug() + .expect('code', 1) + .expect('stdout', /inject context for "root suite": mock create context failed/) + .end(); + }); + }); + + describe('run test', () => { + it('get app error should failed', async () => { + const fixture = path.join(__dirname, 'fixtures/test-case-get-app-failed'); + + await coffee.fork(require.resolve('egg-bin/bin/egg-bin'), [ + 'test', + '-r', require.resolve('../register'), + '--full-trace', + ], { + cwd: fixture, + env: { + EGG_FRAMEWORK: require.resolve('egg'), + }, + }) + // .debug() + .expect('code', 1) + .expect('stdout', /get app for "test case get app error - should not print": mock get app failed/) + .end(); + }); + + it('create context error should failed', async () => { + const fixture = path.join(__dirname, 'fixtures/test-case-create-context-failed'); + + await coffee.fork(require.resolve('egg-bin/bin/egg-bin'), [ + 'test', + '-r', require.resolve('../register'), + '--full-trace', + ], { + cwd: fixture, + env: { + EGG_FRAMEWORK: require.resolve('egg'), + }, + }) + // .debug() + .expect('code', 1) + .expect('stdout', /create context for "test case create context error" error: mock create context failed/) + .end(); + }); + }); });