From 3b0957fd33a638d8c505dd09ba47323f1d006a8a Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 29 Dec 2024 14:30:42 +0800 Subject: [PATCH] fix: support simple test on normal app (#179) ## Summary by CodeRabbit - **New Features** - Added "PRs Welcome" badge to README files. - Added Node.js version badge to Chinese README. - Introduced a simple GET route in the new "helloworld" application. - Established basic configuration for the "helloworld" application. - Created a new `package.json` for the "helloworld" application. - Added a test suite for validating the bootstrap functionality of the "helloworld" application. - **Documentation** - Updated README files with project badges. - **Chores** - Modified test configurations for ESM support. - Updated import paths in type definition files. - Created a new test fixture application "helloworld". - **Refactor** - Removed `src/index.d.ts` type definitions. - Updated `src/bootstrap.ts` to export app instance. - Modified `src/lib/app_handler.ts` to ensure app return. - Refactored test cases in `test/app.test.ts` for improved readability and maintainability. - Updated test suite in `test/app_proxy.test.ts` to run all tests. --- .github/workflows/nodejs.yml | 2 +- README.md | 1 + README.zh_CN.md | 2 + package.json | 1 - src/bootstrap.ts | 3 +- src/index.d.ts | 193 ------------------ src/lib/app_handler.ts | 1 + test/app.test.ts | 11 +- test/app_proxy.test.ts | 2 +- test/bootstrap.test.ts | 117 ++--------- test/fixtures/apps/helloworld/app/router.js | 7 + .../apps/helloworld/config/config.default.js | 3 + test/fixtures/apps/helloworld/package.json | 7 + .../apps/helloworld/test/helloworld.test.js | 11 + {src => test}/index.test-d.ts | 4 +- 15 files changed, 58 insertions(+), 307 deletions(-) delete mode 100644 src/index.d.ts create mode 100644 test/fixtures/apps/helloworld/app/router.js create mode 100644 test/fixtures/apps/helloworld/config/config.default.js create mode 100644 test/fixtures/apps/helloworld/package.json create mode 100644 test/fixtures/apps/helloworld/test/helloworld.test.js rename {src => test}/index.test-d.ts (93%) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 8186fa7..63e9df1 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -12,6 +12,6 @@ jobs: uses: node-modules/github-actions/.github/workflows/node-test.yml@master with: os: 'ubuntu-latest, macos-latest' - version: '18.19.0, 18, 20, 22, 23' + version: '18.19.0, 18, 20, 22' secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.md b/README.md index 2db5e1e..c99a8a1 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Test coverage][codecov-image]][codecov-url] [![npm download][download-image]][download-url] [![Node.js Version](https://img.shields.io/node/v/@eggjs/mock.svg?style=flat)](https://nodejs.org/en/download/) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) [npm-image]: https://img.shields.io/npm/v/@eggjs/mock.svg?style=flat-square [npm-url]: https://npmjs.org/package/@eggjs/mock diff --git a/README.zh_CN.md b/README.zh_CN.md index f9212b2..3b471be 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -4,6 +4,8 @@ [![Node.js CI](https://github.com/eggjs/mock/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/mock/actions/workflows/nodejs.yml) [![Test coverage][codecov-image]][codecov-url] [![npm download][download-image]][download-url] +[![Node.js Version](https://img.shields.io/node/v/@eggjs/mock.svg?style=flat)](https://nodejs.org/en/download/) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) [npm-image]: https://img.shields.io/npm/v/@eggjs/mock.svg?style=flat-square [npm-url]: https://npmjs.org/package/@eggjs/mock diff --git a/package.json b/package.json index 005f6b5..7733ba8 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "@types/mocha": "10", "@types/node": "22", "egg": "beta", - "egg-bin": "beta", "egg-errors": "^2.2.1", "egg-tracer": "^2.0.0", "eslint": "8", diff --git a/src/bootstrap.ts b/src/bootstrap.ts index aceef56..3a94681 100644 --- a/src/bootstrap.ts +++ b/src/bootstrap.ts @@ -13,11 +13,12 @@ if (pkgInfo.eggPlugin) { throw new Error('DO NOT USE bootstrap to test plugin'); } -setupApp(); +const app = setupApp(); export { assert, getBootstrapApp, + app, mm, mock, MockApplication, diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index 86bed07..0000000 --- a/src/index.d.ts +++ /dev/null @@ -1,193 +0,0 @@ -// import { Application, Context, EggLogger } from 'egg'; -// import { MockMate } from 'mm'; -// import { Test } from 'supertest'; -// import { MockAgent } from 'urllib'; -// import { Suite } from 'mocha'; - -// export { MockAgent }; - -// export interface EggTest extends Test { -// unexpectHeader(name: string, b?: Function): EggTest; -// expectHeader(name: string, b?: Function): EggTest; -// } - -// export type Methods = 'get' | 'post' | 'delete' | 'del' | 'put' | 'head' | 'options' | 'patch' | 'trace' | 'connect'; - -// export interface BaseMockApplication extends Application { -// ready(): Promise; -// close(): Promise; -// callback(): any; - -// /** -// * mock Context -// */ -// mockContext(data?: any, options?: any): C; - -// /** -// * mock Context -// */ -// mockContextScope(fn: (ctx: C) => Promise, data?: any): Promise; - -// /** -// * mock cookie session -// */ -// mockSession(data: any): T; - -// mockCookies(cookies: any): T; - -// mockHeaders(headers: any): T; - -// /** -// * Mock service -// */ -// mockService(service: string, methodName: string, fn: any): T; - -// /** -// * mock service that return error -// */ -// mockServiceError(service: string, methodName: string, err?: Error): T; - -// mockHttpclient(mockUrl: string | RegExp, mockMethod: string | string[], mockResult: MockHttpClientResult): Application; - -// mockHttpclient(mockUrl: string | RegExp, mockResult: MockHttpClientResult): Application; - -// mockAgent(): MockAgent; -// mockAgentRestore(): Promise; -// mockRestore(): Promise; - -// /** -// * mock csrf -// */ -// mockCsrf(): T; - -// /** -// * http request helper -// */ -// httpRequest(): { -// [key in Methods]: (url: string) => EggTest; -// } & { -// [key: string]: (url: string) => EggTest; -// }; - -// /** -// * mock logger -// */ -// mockLog(logger?: EggLogger | string): void; -// expectLog(expected: string | RegExp, logger?: EggLogger | string): void; -// notExpectLog(expected: string | RegExp, logger?: EggLogger | string): void; - -// /** -// * background task -// */ -// backgroundTasksFinished(): Promise; -// } - -// export interface ResultObject { -// data?: string | object | Buffer; -// status?: number; -// headers?: any; -// delay?: number; -// persist?: boolean; -// repeats?: number; -// } - -// export type ResultFunction = (url?: string, opts?: any) => ResultObject | string | void; - -// export type MockHttpClientResult = ResultObject | ResultFunction | string; - -// export interface MockOption { -// /** -// * The directory of the application -// */ -// baseDir?: string; - -// /** -// * Custom you plugins -// */ -// plugins?: any; - -// /** -// * The directory of the egg framework -// */ -// framework?: string; - -// /** -// * Cache application based on baseDir -// */ -// cache?: boolean; - -// /** -// * Swtich on process coverage, but it'll be slower -// */ -// coverage?: boolean; - -// /** -// * Remove $baseDir/logs -// */ -// clean?: boolean; - -// /** -// * default options.mockCtxStorage value on each mockContext -// */ -// mockCtxStorage?: boolean; -// } - -// export interface MockClusterOption extends MockOption { -// workers?: number | string; -// cache?: boolean; -// /** -// * opt for egg-bin -// */ -// opt?: object; -// } - -// export type EnvType = 'default' | 'test' | 'prod' | 'local' | 'unittest' | string & {}; -// export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'NONE'; - -// export interface MockApplication extends BaseMockApplication { -// [key: string]: any; -// } - -// export interface EggMock extends MockMate { -// /** -// * Create a egg mocked application -// */ -// app: (option?: MockOption) => MockApplication; - -// /** -// * Create a mock cluster server, but you can't use API in application, you should test using supertest -// */ -// cluster: (option?: MockClusterOption) => MockApplication; - -// /** -// * mock the serverEnv of Egg -// */ -// env: (env: EnvType) => void; - -// /** -// * mock console level -// */ -// consoleLevel: (level: LogLevel) => void; - -// /** -// * set EGG_HOME path -// */ -// home: (homePath: string) => void; - -// /** -// * restore mock -// */ -// restore: () => any; - -// /** -// * If you use mm.app instead of egg-mock/bootstrap to bootstrap app. -// * Should manually call setGetAppCallback, -// * then egg-mock will inject ctx for each test case -// * @param cb -// */ -// setGetAppCallback: (cb: (suite: Suite) => Promise) => void; -// } - -// declare const mm: EggMock; -// export { mm }; -// export default mm; diff --git a/src/lib/app_handler.ts b/src/lib/app_handler.ts index 652fbda..b29b053 100644 --- a/src/lib/app_handler.ts +++ b/src/lib/app_handler.ts @@ -46,6 +46,7 @@ export function setupApp() { afterEach(restore); } } + return app; } let getAppCallback: (suite: unknown, test?: unknown) => any; diff --git a/test/app.test.ts b/test/app.test.ts index 8afe0cb..8b3bea9 100644 --- a/test/app.test.ts +++ b/test/app.test.ts @@ -1,7 +1,6 @@ import { strict as assert } from 'node:assert'; import mm, { MockApplication } from '../src/index.js'; import { getFixtures } from './helper.js'; -import { pending } from 'pedding'; describe('test/app.test.ts', () => { afterEach(mm.restore); @@ -91,15 +90,11 @@ describe('test/app.test.ts', () => { assert((app.options as any).test === 'abc'); }); - it('should emit error when load Application fail', done => { - done = pending(2, done); + it('should emit error when load Application fail', async () => { const baseDir = getFixtures('app-fail'); const app = mm.app({ baseDir, cache: false }); - app.once('error', (err: any) => { - app.close().then(() => done); - assert(/load error/.test(err.message)); - done(); - }); + await assert.rejects(app.ready(), /load error/); + await app.close(); }); it('should FrameworkErrorformater work during app boot', async () => { diff --git a/test/app_proxy.test.ts b/test/app_proxy.test.ts index f0cf7db..c660b56 100644 --- a/test/app_proxy.test.ts +++ b/test/app_proxy.test.ts @@ -298,7 +298,7 @@ describe('test/app_proxy.test.ts', () => { }); }); - describe.only('messenger binding on cluster() mode', () => { + describe('messenger binding on cluster() mode', () => { let app: MockApplication; const baseDir = getFixtures('messenger-binding'); before(() => { diff --git a/test/bootstrap.test.ts b/test/bootstrap.test.ts index 001cdad..12716b9 100644 --- a/test/bootstrap.test.ts +++ b/test/bootstrap.test.ts @@ -1,106 +1,23 @@ -import { strict as assert } from 'node:assert'; +import path from 'node:path'; +import coffee from 'coffee'; +import { importResolve } from '@eggjs/utils'; import { getFixtures } from './helper.js'; -import mm, { mock, MockApplication } from '../src/index.js'; -// TBD: This test case is not working as expected. Need to investigate. describe('test/bootstrap.test.ts', () => { - const baseDir = process.env.EGG_BASE_DIR = getFixtures('app'); - let app: MockApplication; - before(async () => { - const { getBootstrapApp } = await import('../src/bootstrap.js'); - app = getBootstrapApp(); - await app.ready(); - }); - - it('should create app success', () => { - assert(app.baseDir === baseDir); - }); - - it('should mock and mm success', () => { - assert(app.baseDir === baseDir); - mm(app, 'baseDir', 'foo'); - assert(app.baseDir === 'foo'); - mock(app, 'baseDir', 'bar'); - assert.equal(app.baseDir, 'bar'); - }); - - it('should afterEach(mm.restore) success', () => { - assert(app.baseDir === baseDir); - }); - - describe('backgroundTasksFinished()', () => { - it('should wait for background task 1 finished', async () => { - await app.httpRequest() - .get('/counter') - .expect(200) - .expect({ counter: 0 }); - await app.httpRequest() - .get('/counter/plus') - .expect(200) - .expect({ counter: 0 }); - }); - - it('should wait for background task 2 finished', async () => { - await app.httpRequest() - .get('/counter') - .expect(200) - .expect({ counter: 1 }); - await app.httpRequest() - .get('/counter/minus') - .expect(200) - .expect({ counter: 1 }); - }); - - it.skip('should wait for background task 3 finished', async () => { - await app.httpRequest() - .get('/counter') - .expect(200) - .expect({ counter: 0 }); - app.mockContext({ superMan: true }); - await app.httpRequest() - .get('/counter/plus') - .expect(200) - .expect({ counter: 0 }); - }); - - it.skip('should wait for background task 4 finished', async () => { - await app.httpRequest() - .get('/counter') - .expect(200) - .expect({ counter: 10 }); - await app.httpRequest() - .get('/counter/plusplus') - .expect(200) - .expect({ counter: 10 }); - }); - - it.skip('should wait for background task 5 finished', async () => { - await app.httpRequest() - .get('/counter') - .expect(200) - .expect({ counter: 12 }); - app.mockContext({ superMan: true }); - await app.httpRequest() - .get('/counter/plusplus') - .expect(200) - .expect({ counter: 12 }); - }); - - it.skip('should all reset', async () => { - await app.httpRequest() - .get('/counter') - .expect(200) - .expect({ counter: 32 }); - }); - - it('should always return promise instance', async () => { - let p = app.backgroundTasksFinished(); - assert(p.then); - p = app.backgroundTasksFinished(); - assert(p.then); - p = app.backgroundTasksFinished(); - assert(p.then); - await app.backgroundTasksFinished(); + describe('normal app in ESM', () => { + it('should work', async () => { + const eggBinFile = path.join(importResolve('@eggjs/bin/package.json'), '../bin/run.js'); + await coffee.fork(eggBinFile, [ + 'test', + '--no-typescript', + '-r', getFixtures('../../dist/esm/register.js'), + ], { + cwd: getFixtures('apps/helloworld'), + }) + .debug() + .expect('code', 0) + .expect('stdout', /\d+ passing/) + .end(); }); }); }); diff --git a/test/fixtures/apps/helloworld/app/router.js b/test/fixtures/apps/helloworld/app/router.js new file mode 100644 index 0000000..41f90ab --- /dev/null +++ b/test/fixtures/apps/helloworld/app/router.js @@ -0,0 +1,7 @@ +export default app => { + app.get('/', async ctx => { + ctx.body = { + hello: 'world', + }; + }); +}; diff --git a/test/fixtures/apps/helloworld/config/config.default.js b/test/fixtures/apps/helloworld/config/config.default.js new file mode 100644 index 0000000..cbb38cd --- /dev/null +++ b/test/fixtures/apps/helloworld/config/config.default.js @@ -0,0 +1,3 @@ +export default { + keys: '123456', +}; diff --git a/test/fixtures/apps/helloworld/package.json b/test/fixtures/apps/helloworld/package.json new file mode 100644 index 0000000..13847b5 --- /dev/null +++ b/test/fixtures/apps/helloworld/package.json @@ -0,0 +1,7 @@ +{ + "name": "helloworld", + "egg": { + "typescript": false + }, + "type": "module" +} diff --git a/test/fixtures/apps/helloworld/test/helloworld.test.js b/test/fixtures/apps/helloworld/test/helloworld.test.js new file mode 100644 index 0000000..e307a55 --- /dev/null +++ b/test/fixtures/apps/helloworld/test/helloworld.test.js @@ -0,0 +1,11 @@ +import { app } from '../../../../../dist/esm/bootstrap.js'; + +describe('bootstrap test', () => { + it('should GET /', () => { + return app.httpRequest() + .get('/') + .expect({ + hello: 'world', + }); + }); +}); diff --git a/src/index.test-d.ts b/test/index.test-d.ts similarity index 93% rename from src/index.test-d.ts rename to test/index.test-d.ts index 3b889d5..b7942d3 100644 --- a/src/index.test-d.ts +++ b/test/index.test-d.ts @@ -1,7 +1,7 @@ import { expectType } from 'tsd'; import { ContextDelegation } from 'egg'; -import { MockApplication, MockAgent, ResultObject } from './index.js'; -import { getBootstrapApp, mock, mm } from './bootstrap.js'; +import { MockApplication, MockAgent, ResultObject } from '../src/index.js'; +import { getBootstrapApp, mock, mm } from '../src/bootstrap.js'; const app = getBootstrapApp(); expectType(app);