如何用unit test测试controller_如何为团队量身定制 EggJS 目录挂载规范?

e5a18d05b53fcad38e0502f8457de3d3.png

背景

我们知道 Egg 内置了 Controller/Service 等目录规范,会自动挂载到对应的 ctx 上。

而在实际业务开发中,往往需要为我们的团队定制一些新规范,如何实现呢?

Egg 的定位是框架的框架,因此这类的能力是天然具备的,开发者只需要简单配置下即可。

下面我们来一起定制以下规范:

demo
└── app
    ├── enum
    |   ├── error.js
    |   └── status.js
    ├── utils
    |   └── formatter.js
    └── rpc
        └── user.js

app.enum

全局静态枚举,约定:app/enum/** 的文件都被挂载到 app.enum

只需要一个简单的配置:

// config/config.default.js
exports.customLoader = {
  enum: {
    directory: 'app/enum',
  },
};

然后编写 app/enum/error.js 来提供对应的定义:

// app/enum/error.js
exports.ERR_AUTH = {
  code: '403',
  msg: 'not perm',
};

exports.ERR_NOTFOUND = {
  code: '404',
  msg: 'not found',
};

就可以直接在业务代码中使用了:

// app/controller/home.js
class HomeController extends Controller {
  async index() {
    console.log(this.app.enum.error.ERR_AUTH);
  }
}

app.utils

经常有同学反馈,内置的 Helper 太简单的,只支持单文件。实际上 Helper 的定位是给模板渲染用的,如果大家有一些公共方法,可以自定义挂载到 app.utils 上。

配置类似上面的方式,此处演示下如何获取 app 对象:

// config/config.default.js
exports.customLoader = {
  utils: {
    directory: 'app/utils',
    inject: 'app',
  },
};

定义:

// app/utils/formatter.js
module.exports = class Formatter {
  constructor(app) {
    this.app = app;
    this.config = app.config;
    this.logger = app.logger;
  }

  // 会被挂载为 `app.utils.formatter.random()`
  random(max) {
    return Math.floor(Math.random() * Math.floor(max));
  }
};

当然,你也可以简化为:

module.exports = app => {
 return {
   random() {},
 };
};

ctx.rpc

内置的Service 一般用来作为业务逻辑层,但有些时候,我们也需要独立出一层,如把跟后端相关的逻辑,都封装为 rpc 的概念。

app/rpc/** 的文件都被挂载到 ctx.rpc

一样只需要简单配置:

// config/config.default.js
exports.customLoader = {
  rpc: {
    directory: 'app/rpc',
    inject: 'ctx',
  },
  // ...
};

然后编写 app/rpc/test.js 来提供对应的定义:

// app/rpc/test.js
module.exports = class TestRpc {
  constructor(ctx) {
    this.ctx = ctx;
    this.config = ctx.app.config;
    this.logger = ctx.logger;
  }

  async sayHi(name) {
    console.log(this.ctx.app.config.env);
    return `hi, ${name}`;
  }
};

就可以直接在业务代码中使用了:

// app/controller/home.js
class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    ctx.body = await ctx.rpc.test.sayHi('egg');
  }
}

生成声明

由于 Egg 是动态挂载的,如需 TS 和智能提示支持,需要通过我们的 ets 来自动生成映射。

简单配置下 package.json,重启即可自动生成对应的 typings。

{
  "name: "egg-showcase",
  "egg": {
    "declarations": true,
    "tsHelper": {
      "watchDirs": {
        "enum": {
          "enabled": true,
          "directory": "app/enum",
          "declareTo": "Application.enum"
        },
        "utils": {
          "enabled": true,
          "directory": "app/utils",
          "declareTo": "Application.utils"
        },
        "rpc": {
          "enabled": true,
          "directory": "app/rpc",
          "declareTo": "Context.rpc"
        }
      }
    }
  },
}

享受智能提示吧:

63a774452900b80f78ea13930d5fcaab.png

规范化 && 示例

上面的演示,都是在应用里面配置,但往往我们需要的是制定团队规范,因此推荐把上述配置封装到插件,或者上层框架里,这样可以统一管控和升级。

除此之外,如果我们期望想 Egg 内置规范那样,希望在插件里面定义的 rpc 文件也能被自动加载,怎么办呢?

很简单,只需要多配置下 loadunit: true 即可。

// config/config.default.js
exports.customLoader = {
  rpc: {
    directory: 'app/rpc',
    inject: 'ctx',
    loadunit: true,
  },
  // ...
};

最后,别忘了写单测哈。

完整的示例代码,可以参见:https://github.com/atian25/egg-showcase/pull/13/files


内部原理

上述的配置,其实就是调用了 Egg 内置的 app.loader API 即可,等价于:

// some_plugin/app.js
module.exports = class AppLifeCycle {
  constructor(app) {
    this.app = app;
  }

  configDidLoad() {
    // 所有的配置已经加载完毕,可以用来加载应用自定义的文件,初始化自定义的服务

    // 只加载应用目录
    const enumPaths = path.join(this.app.config.baseDir, 'app/enum');
    this.app.loader.loadToApp(enumPaths, 'enum');

    // 加载所有的 loadunit
    const rpcPaths = this.app.loader.getLoadUnits().map(unit => path.join(unit.path, 'app/rpc'));
    this.app.loader.loadToContext(rpcPaths, 'rpc');
  }
};

Egg 进一步封装了这个能力,使得开发者只需要简单的一个配置即可加载。

有兴趣的也可以看下 egg-core 这个库的源码。


写在最后

从本文中,读者可以学习到如何在 Egg 上定制适合自己团队的目录规范。这也是 Egg 设计理念很重要的一点,它本身的定位就是框架的框架。我们内部的很多插件,都是通过这个方式来实现标准化的。

不过,在 2020 这个时间点,框架其实只是整个研发解决方案里面的一个单点,我们还需要打通上下游的 PaaS、云服务、部署平台等等深水区的事,如果你对此感兴趣的话,欢迎加入我们。


我们隶属于玉伯的蚂蚁体验技术部。

目前正致力于 为蚂蚁提供 轻研发、免运维 的下一代 Node.js 研发方案。

目前团队活多人少有挑战,撸起袖子拼命干,Base 地可以选:广州、杭州、上海

  • 这些年的体验技术部(六) · Node.js 基础服务 - 摸爬滚打才不负功名尘土
  • 招聘 JD 和当前我们要做的事

6c1ef17655ea2a42ea3dd81ade6f7465.png
欢迎加微信沟通
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值