├── package.json
├── app.js (app.js 和 agent.js 用于自定义启动时的初始化工作)
├── agent.js (可选)
├── app
| ├── router.js(用于配置 URL 路由规则)
│ ├── controller(用于解析用户的输入,处理后返回相应的结果)
│ | └── home.js
│ ├── service (用于编写业务逻辑层,可选)
│ | └── user.js
│ ├── middleware (用于编写中间件,可选)
│ | └── response_time.js
│ ├── schedule (用于定时任务,可选)
│ | └── my_task.js
│ ├── public (用于放置静态资源,可选)
│ | └── reset.css
│ ├── extend (用于框架的扩展,可选)
│ | └── application.js app 对象指的是 Koa 的全局应用对象,全局只有一个,在应用启动时被创建。
│ ├── context.js (Context 指的是 Koa 的请求上下文,这是 请求级别 的对象)
│ ├── request.js (Request 对象和 Koa 的 Request 对象相同,是 请求级别 的对象)
│ ├── response.js (Response 对象和 Koa 的 Response 对象相同,是 请求级别 的对象)
│ ├── helper.js (Helper 函数用来提供一些实用的 utility 函数)
│ ├── view (用于放置模板文件)
│ | └── home.tpl
├── |── model (用于放置领域模型)
│ | └── home.tpl
│ └── extend (用于框架的扩展)
│ ├── helper.js (可选)
│ ├── request.js (可选)
│ ├── response.js (可选)
│ ├── context.js (可选)
│ ├── application.js (可选)
│ └── agent.js (可选)
├── config(用于编写配置文件)
| ├── plugin.js(用于配置需要加载的插件)
| ├── config.default.js
│ ├── config.prod.js
| ├── config.test.js (可选)
| ├── config.local.js (可选)
| └── config.unittest.js (可选)
└── test(用于单元测试)
├── middleware
| └── response_time.test.js
└── controller
└── home.test.js
文件 | app | ctx | service | config | logger | helper |
---|---|---|---|---|---|---|
Controller | this.app | this.ctx | this.service | this.config | this.logger | this.app.helper |
Service | this.app | this.ctx | this.service | this.config | this.logger | this.app.helper |
ctx.helper
mkdir egg-news
cd egg-news
npm init -y
npm i egg --save
npm i egg-bin --save-dev
npm i mockjs express morgan egg-mock --save
"scripts": {
"dev": "egg-bin dev"
}
├─app
│ │─router.js
│ ├─controller
│ │ news.js
├─config
│ config.default.js
|─package.json
app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/news', controller.news.index);
}
app\controller\news.js
const { Controller } = require('egg');
class NewsController extends Controller {
async index() {
this.ctx.body = 'hello world';
}
}
module.exports = NewsController;
config\config.default.js
exports.keys = 'zhufeng';
├─app
│ │─router.js
│ ├─controller
│ │ news.js
│ ├─public
│ │ ├─css
│ │ │ bootstrap.css
│ │ └─js
│ │ bootstrap.js
│ └─view
│ index.html
├─config
│ config.default.js
│ plugin.js
npm install egg-view-nunjucks --save
{ROOT}\config\plugin.js
exports.nunjucks = {
enable: true,
package: 'egg-view-nunjucks'
}
{ROOT}\config\config.default.js
module.exports=app => {
let config={};
config.keys='zhufeng';
config.view={
defaultExtension: '.html',
defaultViewEngine: 'nunjucks',
mapping: {
'.html':'nunjucks'
}
}
return config;
}
app\view\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/public/css/bootstrap.css">
<title>新闻列表</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% for item in list%}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="text-center">{{item.title}}</h3>
</div>
<div class="panel-body">
<img src="{{item.image}}" class="img-responsive center-block">
</div>
<div class="panel-footer">
<h3 class="text-center">创建时间: {{item.createAt}}</h3>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</body>
</html>
app\controller\news.js
const {Controller}=require('egg');
class NewsController extends Controller{
async index() {
const {ctx}=this;
const list=[
{
id: '45154322_0',
title: '世界首富早晚是这个人,坐拥7家独角兽公司,估值破数万!',
url: 'http://tech.ifeng.com/a/20180904/45154322_0.shtml',
image:'http://p0.ifengimg.com/pmop/2018/0905/CFFF918B94D561D2A61FB434ADA81589E8972025_size41_w640_h479.jpeg',
createAt:new Date().toLocaleString()
},
{
id: '16491630_0',
title: '支付宝们来了!将来人民币会消失吗?',
url: 'http://finance.ifeng.com/a/20180907/16491630_0.shtml',
image:'http://p0.ifengimg.com/pmop/2018/0907/2AF684C2EC49B7E3C17FCB13D6DEEF08401D4567_size27_w530_h369.jpeg',
createAt:new Date().toLocaleString()
},
{
id: '2451982',
title: '《福布斯》专访贝索斯:无业务边界的亚马逊 令对手生畏的CEO',
url: 'https://www.jiemian.com/article/2451982.html',
image:'https://img1.jiemian.com/101/original/20180907/153628523948814900_a580x330.jpg',
createAt:new Date().toLocaleString()
}
];
await ctx.render('index',{list});
}
}
module.exports=NewsController;
在实际应用中,Controller 一般不会自己产出数据,也不会包含复杂的逻辑,复杂的过程应抽象为业务逻辑层 Service。
config.default.js
config.news={
pageSize:10,
newsListUrl:'http://localhost:3000/news'
}
mock.js
let Mock = require('mockjs');
let express = require('express');
let logger = require('morgan');
let app = express();
app.use(logger('dev'));
app.get('/news', function (req, res) {
let result = Mock.mock(
{
"data|10": [{
"id": "@id",
"title": "@csentence",
"url": "@url",
"image": "@image(600X500)",
"createAt": "@datetime",
}]
});
res.json(result);
});
app.get('/cache', function (req, res) {
res.json({ title: '新闻标题' + Date.now() });
});
app.listen(3000, () => { console.log('mock server is running at port 3000') });
app/service/news.js
const {Service}=require('egg');
class NewsService extends Service {
async list(pageNum,pageSize) {
const {ctx}=this;
const {newsListUrl}=this.config.news;
const result=await ctx.curl(newsListUrl,{
method: 'GET',
data: {
pageNum,pageSize
},
dataType:'json'
});
return result.data.data;
}
}
module.exports=NewsService;
app/controller/news.js
const {Controller}=require('egg');
class NewsController extends Controller{
async index() {
const {ctx,service}=this;
let {pageNum=1,pageSize=this.config.news.pageSize}=ctx.query;
const list=await service.news.list(pageNum,pageSize);
await ctx.render('index',{list});
}
}
module.exports=NewsController;
我们还会有许多场景需要执行一些定时任务,例如:
app\schedule\update_cache.js
const { Subscription } = require('egg');
class UpdateCache extends Subscription {
// 通过 schedule 属性来设置定时任务的执行间隔等配置
static get schedule() {
return {
interval: '1m', // 1 分钟间隔
type: 'all', // 指定所有的 worker 都需要执行
};
}
// subscribe 是真正定时任务执行时被运行的函数
async subscribe() {
console.log('subscribe');
const res = await this.ctx.curl(this.config.cache.url, {
dataType: 'json',
});
this.ctx.app.cache = res.data;
}
}
module.exports = UpdateCache;
${appInfo.root}/logs/{app_name}/egg-schedule.log
config\config.default.js
+config.cache = {
+ url: 'http://localhost:3000/cache',
+}
mock.js
app.get('/cache',function(req,res){
res.json({title:'新闻列表'+Date.now()});
});
app\controller\news.js
const {Controller} = require('egg');
class NewsController extends Controller{
async index(){
const {ctx,service}=this;
let {pageNum=1,pageSize=this.config.news.pageSize}=ctx.query;
const list=await service.news.list(pageNum,pageSize);
+ await ctx.render('index',{list,title:this.app.cache?this.app.cache.title:'新闻列表'});
}
}
module.exports = NewsController;
app\view\index.html
<div class="container">
+ <h3>{{title}}</h3>
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% for item in list%}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="text-center">{{item.title}}</h3>
</div>
<div class="panel-body">
<img src="{{item.image}}" class="img-responsive center-block">
</div>
<div class="panel-footer">
<h3 class="text-center">创建时间: {{item.createAt}}</h3>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
module.exports = app => {
app.beforeStart(async () => {
// 保证应用启动监听端口前数据已经准备好了
// 后续数据的更新由定时任务自动触发
await app.runSchedule('update_cache');
});
};
npm i --save egg-mysql
config/plugin.js
exports.mysql = {
enable: true,
package: 'egg-mysql',
};
CREATE TABLE `news` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`createAt` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;
INSERT INTO `news` VALUES (1, '世界首富早晚是这个人,坐拥7家独角兽公司,估值破数万!', 'http://tech.ifeng.com/a/20180904/45154322_0.shtml', 'http://p0.ifengimg.com/pmop/2018/0905/CFFF918B94D561D2A61FB434ADA81589E8972025_size41_w640_h479.jpeg', '2019-06-08 22:07:29');
INSERT INTO `news` VALUES (2, '支付宝们来了!将来人民币会消失吗?', 'http://finance.ifeng.com/a/20180907/16491630_0.shtml', 'http://p0.ifengimg.com/pmop/2018/0907/2AF684C2EC49B7E3C17FCB13D6DEEF08401D4567_size27_w530_h369.jpeg', '2019-06-08 22:08:24');
INSERT INTO `news` VALUES (3, '《福布斯》专访贝索斯:无业务边界的亚马逊 令对手生畏的CEO', 'https://www.jiemian.com/article/2451982.html', 'https://img1.jiemian.com/101/original/20180907/153628523948814900_a580x330.jpg', '2019-06-08 22:17:16');
config/config.${env}.js
config.mysql = {
// 单数据库信息配置
client: {
// host
host: 'localhost',
// 端口号
port: '3306',
// 用户名
user: 'root',
// 密码
password: 'root',
// 数据库名
database: 'cms'
},
// 是否加载到 app 上,默认开启
app: true,
// 是否加载到 agent 上,默认关闭
agent: false,
};
app\service\news.js
const {Service}=require('egg');
class NewsService extends Service {
async list(pageNum,pageSize) {
const {ctx}=this;
let result = await this.app.mysql.query('select * from news');
return result;
}
}
module.exports=NewsService;
egg-sequelize
插件会辅助我们将定义好的Model对象加载到app
和ctx
上$ npm install --save egg-sequelize mysql2
exports.sequelize = {
enable: true,
package: 'egg-sequelize',
};
config/config.default.js
config.sequelize = {
dialect: 'mysql',
host: 'localhost',
port: 3306,
username: "root",
password: "root",
database: 'cms-development'
};
config/config.test.js
module.exports=app => {
let config={};
config.sequelize = {
dialect: 'mysql',
host: 'localhost',
port: 3306,
username: "root",
password: "root",
database: 'cms-test',
};
return config;
}
sequelize-cli
工具来实现 Migrations
npm install --save sequelize sequelize-cli
Migrations
相关的内容都放在database
目录下,所以我们在项目根目录下新建一个 .sequelizerc
配置文件const path = require('path');
module.exports = {
config: path.join(__dirname, 'database/config.json'),
'migrations-path': path.join(__dirname, 'database/migrations'),
'seeders-path': path.join(__dirname, 'database/seeders'),
'models-path': path.join(__dirname, 'app/model'),
};
database/config.json
文件和 database/migrations
npx sequelize init:config
npx sequelize init:migrations
NODE_ENV
的值,默认就是 development
set NODE_ENV=test
config.json
{
"development": {
"username": "root",
"password": "root",
"database": "cms-development",
"host": "127.0.0.1",
"dialect": "mysql",
"operatorsAliases": false
},
"test": {
"username": "root",
"password": "root",
"database": "cms-test",
"host": "127.0.0.1",
"dialect": "mysql",
"operatorsAliases": false
}
}
npx sequelize migration:generate --name=init-users
执行完后会在 database/migrations 目录下生成一个 migration 文件(${timestamp}-init-users.js)
database\migrations\20190608143311-init-users.js
module.exports = {
// 在执行数据库升级时调用的函数,创建 users 表
up: async (queryInterface, Sequelize) => {
const { INTEGER, DATE, STRING } = Sequelize;
await queryInterface.createTable('users', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
});
},
// 在执行数据库降级时调用的函数,删除 users 表
down: async queryInterface => {
await queryInterface.dropTable('users');
},
};
# 升级数据库
npx sequelize db:migrate
# 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
# npx sequelize db:migrate:undo
# 可以通过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all
sequelize seed:create --name init-users
npx sequelize db:seed:all
npx sequelize db:seed:all --env development
database\seeders\20190803152323-init-users.js
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('users', [{
name: 'zhufeng',
age:1,
created_at: new Date(),
updated_at: new Date()
},{
name: 'jiagou',
age:2,
created_at: new Date(),
updated_at: new Date()
}],{});
},
down: (queryInterface, Sequelize) => {
return queryInterface.bulkDelete('users', null, {});
}
};
app\model\user.js
module.exports = app => {
const { STRING, INTEGER, DATE } = app.Sequelize;
const User = app.model.define('User', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
});
return User;
};
这个 Model 就可以在 Controller 和 Service 中通过 app.model.User 或者 ctx.model.User 访问到了
app\router.js
module.exports = app => {
const { router, controller } = app;
router.get('/news', controller.news.index);
router.get('/users', controller.users.index);
}
app\controller\users.js
const { Controller } = require('egg');
class UserController extends Controller {
async index() {
const {ctx,service}=this;
ctx.body = await ctx.model.User.findAll();
}
}
module.exports = UserController;
npx sequelize db:migrate --env test
npm install --save-dev factory-girl
test/factories.js
const { factory } = require('factory-girl');
module.exports = app => {
// 可以通过 app.factory 访问 factory 实例
app.factory = factory;
// 定义 user 和默认数据
factory.define('user', app.model.User, {
name: factory.sequence('User.name', n => `name_${n}`),
age: 18,
});
};
test/.setup.js
const { app } = require('egg-mock/bootstrap');
const factories = require('./factories');
before(() => factories(app));
afterEach(async () => {
await Promise.all([
app.model.User.destroy({ truncate: true, force: true }),
]);
});
test/app/controller/users.test.js
// test/app/controller/users.test.js
const { assert, app } = require('egg-mock/bootstrap');
describe('test/app/controller/users.test.js', () => {
describe('GET /users', () => {
it('should work', async () => {
// 通过 factory-girl 快速创建 user 对象到数据库中
await app.factory.createMany('user', 3);
const res = await app.httpRequest().get('/users');
assert(res.status === 200);
assert(res.body.length === 3);
assert(res.body[0].name);
assert(res.body[0].age);
});
});
});
"scripts": {
"dev": "egg-bin dev",
+ "test": "egg-bin test"
}
npm run test
// config/config.default.js
exports.i18n = {
defaultLocale: 'zh-CN',
};
多种语言的配置是独立的,统一存放在 config/locale/*.js 下。
config/locale/en-US.js
module.exports = {
Email: 'Email',
'Welcome back, %s!': 'welcome back,%s!',
'Hello {0}! My name is {1}.': '你好 {0}! 我的名字叫 {1}。',
};
config/locale/zh-CN.js
module.exports = {
Email: '邮箱',
'Welcome back, %s!': '欢迎回来,%s!',
'Hello {0}! My name is {1}.': '你好 {0}! 我的名字叫 {1}.',
};
ctx.__('Email')
// zh-CN => 邮箱
// en-US => Email
app\router.js
router.get('/hello', controller.news.hello);
app\controller\news.js
const { Controller } = require('egg');
class NewsController extends Controller {
async index() {
const { ctx, service } = this;
let { pageNum = 1, pageSize = this.config.news.pageSize } = ctx.query;
const list = await service.news.list(pageNum, pageSize);
+ await ctx.render('index', { list, name: 'zhufeng', names: ['zhufeng', 'jiagou'] });
}
async hello(){
const {ctx}=this;
+ let email = ctx.__('Email');
+ let welcome = ctx.__('Welcome back, %s!', 'zhufeng');
+ let Hello = ctx.__('Hello {0}! My name is {1}.', ['zhufeng','jiagou']);
+ ctx.body = email+welcome+Hello;
}
}
module.exports = NewsController;
app\view\index.html
{{__('Email')}}
{{__('Welcome back, %s!', name)}}
{{__('Hello {0}! My name is {1}.', names)}}
app/extend
目录下提供扩展脚本即可utility
函数。ctx.helper
访问到 helper 对象app\extend\helper.js
const moment=require('moment');
moment.locale('zh-cn');
exports.fromNow=dateTime => moment(new Date(dateTime)).fromNow();
app\controller\news.js
class NewsController extends Controller {
async index() {
const { ctx, service } = this;
let { pageNum = 1, pageSize = this.config.news.pageSize } = ctx.query;
const list = await service.news.list(pageNum, pageSize);
+ list.forEach(item => {
+ item.createAt = ctx.helper.fromNow(item.createAt);
+ });
await ctx.render('index', { list, name: 'zhufeng', names: ['zhufeng', 'jiagou'] });
}
}
或者 app\view\index.html
<div class="panel-footer">
+ <h3 class="text-center">创建时间: {{helper.fromNow(item.createAt)}}</h3>
</div>
app/middleware/robot.js
module.exports=(options,app) => {
return async function(ctx,next) {
const source=ctx.get('user-agent')||'';
const matched=options.ua.some(ua => ua.test(source));
if (matched) {
ctx.status=403;
ctx.body='你没有访问权限';
} else {
await next();
}
}
}
config.default.js
config.middleware=[
'robot'
]
config.robot={
ua: [
/Chrome/
]
}
curl -v --user-agent 'Chrome' http://127.0.0.1:7001/news
curl -v --user-agent 'Chrome' http://127.0.0.1:7001/news
框架有两种方式指定运行环境:
config/env
文件指定,该文件的内容就是运行环境,如 prod。EGG_SERVER_ENV
环境变量指定。app.config.env
来表示应用当前的运行环境。config.local.js
, config.prod.js
等等EGG_SERVER_ENV | 说明 |
---|---|
local | 本地开发环境 |
prod | 生产环境 |
npm install cross-env --save-dev
"scripts": {
"dev": "cross-env EGG_SERVER_ENV=local egg-bin dev",
"debug": "egg-bin debug"
}
test
目录为存放所有测试脚本的目录,测试所使用到的 fixtures
和相关辅助脚本都应该放在此目录下。test
├── controller
│ └── news.test.js
└── service
└── news.test.js
统一使用 egg-bin 来运行测试脚本, 自动将内置的 Mocha、co-mocha、power-assert,nyc 等模块组合引入到测试脚本中, 让我们聚焦精力在编写测试代码上,而不是纠结选择那些测试周边工具和模块。
"scripts": {
"test": "egg-bin test",
"cov": "egg-bin cov"
}
egg-mock
, 有了它我们就可以非常快速地编写一个 app 的单元测试,并且还能快速创建一个 ctx 来测试它的属性、方法和 Service 等。npm i egg-mock -D
在测试运行之前,我们首先要创建应用的一个 app 实例, 通过它来访问需要被测试的 Controller、Middleware、Service 等应用层代码。
// test/controller/news.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/controller/news.test.js', () => {
});
test/order.test.js
describe('egg test', () => {
before(() => console.log('order 1'));
before(() => console.log('order 2'));
after(() => console.log('order 6'));
beforeEach(() => console.log('order 3'));
afterEach(() => console.log('order 5'));
it('should worker', () => console.log('order 4'));
});
test/controller/news.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/controller/news.test.js', () => {
it('should get a ctx', () => {
const ctx=app.mockContext({
session: {
user:{name:'zhufeng'}
}
});
assert(ctx.method === 'GET');
assert(ctx.url==='/');
assert(ctx.session.user.name == 'zhufeng');
});
});
test/controller/news.test.js
/*
it('promise',() => {
return app.httpRequest().get('/news').expect(200);
});
*/
/*
it('callback',(done) => {
app.httpRequest().get('/news').expect(200,done);
});
*/
/*
it('async',async () => {
await app.httpRequest().get('/news').expect(200);
});
*/
test/controller/user.test.js
app.httpRequest()
是 egg-mock
封装的 SuperTest 请求实例app/router.js
router.get('/add',controller.user.add);
router.post('/doAdd',controller.user.doAdd);
app/controller/user.js
const {Controller}=require('egg');
let users=[];
class UserController extends Controller{
async index() {
let {ctx}=this;
await ctx.render('user/list',{users});
}
async add() {
let {ctx}=this;
await ctx.render('user/add',{});
}
async doAdd() {
let {ctx}=this;
let user=ctx.request.body;
user.id=users.length>0?users[users.length-1].id+1:1;
users.push(user);
ctx.body = user;
}
}
module.exports=UserController;
test/controller/user.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
it('test post',async () => {
let user={username: 'zhufeng'};
app.mockCsrf();
let response=await app.httpRequest().post('/doAdd').send(user).expect(200);
assert(response.body.id == 1);
});
ctx.service.${serviceName}
拿到 Service 实例, 然后调用 Service 方法即可。test/service/user.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
const {app,assert}=require('egg-mock/bootstrap');
describe('test/service/news.test.js',() => {
it('newsService',async () => {
let ctx=app.mockContext();
let result=await ctx.service.news.list(1,5);
assert(result.length == 3);
});
});
应用可以对 Application、Request、Response、Context 和 Helper 进行扩展。 我们可以对扩展的方法或者属性针对性的编写单元测试。
egg-mock 创建 app 的时候,已经将 Application 的扩展自动加载到 app 实例了, 直接使用这个 app 实例访问扩展的属性和方法即可进行测试。
app/extend/application.js
let cacheData={};
exports.cache={
get(key) {
return cacheData[key];
},
set(key,val) {
cacheData[key]=val;
}
}
test/app/extend/cache.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/app/extend/cache.test.js', () => {
it('cache',async () => {
app.cache.set('name','zhufeng');
assert(app.cache.get('name') == 'zhufeng');
});
});
app.mockContext()
步骤来模拟创建一个 Context 对象。app\extend\context.js
exports.language=function () {
return this.get('accept-language');
}
test/app/extend/context.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/app/extend/context.test.js',() => {
let language="zh-cn";
it('test language',async () => {
const ctx=app.mockContext({headers: {'Accept-Language':language}});
//console.log('ctx.lan',ctx.lan())
assert(ctx.language() == language);
});
});
通过 ctx.request 来访问 Request 扩展的属性和方法,直接即可进行测试。 app\extend\request.js
module.exports={
get isChrome() {
const userAgent=this.get('User-Agent').toLowerCase();
return userAgent.includes('chrome');
}
}
test\extend\request.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/app/extend/request.test.js',() => {
it('cache',async () => {
const ctx=app.mockContext({
headers: {
'User-Agent':'I love Chrome'
}
});
assert(ctx.request.isChrome);
});
});
Response 测试与 Request 完全一致。 通过 ctx.response 来访问 Response 扩展的属性和方法,直接即可进行测试。 app\extend\response.js
module.exports = {
get isSuccess() {
return this.status === 200;
},
};
test\extend\response.test.js
describe('isSuccess()', () => {
it('should true', () => {
const ctx = app.mockContext();
ctx.status = 200;
assert(ctx.response.isSuccess === true);
});
it('should false', () => {
const ctx = app.mockContext();
ctx.status = 404;
assert(ctx.response.isSuccess === false);
});
});
app\extend\helper.js
module.exports = {
money(val) {
const lang = this.ctx.get('accept-language');
if (lang.includes('zh-cn')) {
return `¥ ${val}`;
}
return `$ ${val}`;
},
};
test\extend\helper.test.js
describe('money()', () => {
it('should RMB', () => {
const ctx = app.mockContext({
// 模拟 ctx 的 headers
headers: {
'Accept-Language': 'zh-cn',
},
});
assert(ctx.helper.money(100) === '¥ 100');
});
it('should US Dolar', () => {
const ctx = app.mockContext();
assert(ctx.helper.money(100) === '$ 100');
});
});
app.runSchedule(schedulePath)
来运行一个定时任务app.runSchedule
接受一个定时任务文件路径(app/schedule 目录下的相对路径或者完整的绝对路径)
,执行对应的定时任务,返回一个 Promise
update_cache.test.js
const mock = require('egg-mock');
const assert = require('assert');
it('should schedule work fine', async () => {
const app = mock.app();
await app.ready();
await app.runSchedule('update_cache');
assert(app.cache);
});
$ npm i egg-scripts --save
添加 npm scripts 到 package.json:
{
"scripts": {
"start": "egg-scripts start --daemon",
"stop": "egg-scripts stop"
}
}
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Egg",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "npm",
"windows": { "runtimeExecutable": "npm.cmd" },
"runtimeArgs": [ "run", "debug" ],
"console": "integratedTerminal",
"protocol": "auto",
"restart": true,
"port": 9229,
"autoAttachChildProcesses": true
}
]
}