Node.js 与 Typescript 和 Mysql 测试路由模拟服务
Posted
技术标签:
【中文标题】Node.js 与 Typescript 和 Mysql 测试路由模拟服务【英文标题】:Node.js with Typescript and Mysql Testing routes mock services 【发布时间】:2018-06-17 04:17:18 【问题描述】:我正在使用 node.js 和 typescript 来构建一个 web api。对于我使用 mariadb (mysql) 的数据库。我已经使用存储库模式 + 工作单元构建了项目(我不是 .NET 开发人员,但我在一家使用 .NET 的公司工作)。 我正在尝试编写测试,到目前为止,我已经能够使用 chai 和 typemoq 库为我的模拟编写域模型和服务。我想存储库也是可行的。我(目前)面临问题的地方是我使用我的服务的路线。是否可以测试路线并模拟服务?我看过许多节点教程,他们在其中为路由编写测试,但他们只是检查响应而不模拟任何内容。这不是类似于集成测试而不是单元测试吗?有人如何将 TDD 应用于节点?
这是一个例子:
router.post('/register', (req, res, next) =>
try
let newUser = <RegisterUserDTO>req.body;
let uow = <IUnitOfWork>req.uow;
let userRepository = new UserRepository(uow);
let userService: IUserService = new UserService(userRepository);
userService.getByUsername(newUser.username)
.then((user:User) =>
if(!user)
userService.getByEmail(newUser.email).then(user =>
if(!user)
userService.insertUser(newUser)
.then((result) =>
res.json(
success: true,
msg: "User registered",
userId: result.insertId
);
)
.catch(err =>
res.json( errorMessage: err.message, errorStackTrace: err.stack );
);
else
res.json( success: false, msg: "This email already exists" );
);
else
res.json( success: false, msg: "This username already exists" );
)
catch(err)
res.json( errorMessage: err.message, errorStackTrace: err.stack );
);
我将如何模拟 userService 并测试这条路线? 这是使用节点的“错误”方式吗?
提前致谢
【问题讨论】:
您最终找到解决方案了吗?如果没有,我可以向您展示我使用的工具/方法吗? 是的,但请告诉我您的方法。我所做的是创建一个假服务器并在其构造函数中传递任何依赖项。因此,通过使用 sinon.js,我创建了我需要的存根/模拟并将它们提供给假服务器。 我也用sinon。今晚晚些时候我将使用您的代码发布一个示例。 【参考方案1】:为了使测试更容易,我会将更多逻辑推送到您的服务中,并划分路由和服务的职责。在你的路由中拥有大量的 if 语句,甚至是一个存储库创建将使得测试变得非常困难,并且赋予路由更大的责任,而不仅仅是路由请求和响应。
这是构建它的一种方法。
user.route.js
const userService = require("../services/user.services.js")
router.post("/register",function(req)
var potentialUser = webAdapter.getUserFromRequest(req)
var response = userService.register(potentialUser) //returns a promsie
webWriterUtility.json(response)
UserRoute 的责任
如果您分析上面的代码,则路由的职责不再是确定适当的响应消息,检查用户是否存在等。它的工作是从 userService 获取响应并将其发送给用户.这简化了我需要测试的内容。由于在路由测试中我只关心路由内部立即发生的事情,因此我可以完全删除 userService.register。 userService 是否返回 promise resolve 或 promise reject 无关紧要,只要路由正确处理这些场景即可。
上面的代码和 URL “/user/register” 可以很容易地用 sinon.js 进行测试。 Node 只加载一次“必需”模块,因此在您对用户路由的单元测试中,您可以加载 userService,将存根添加到注册函数,然后当 UserRoute 加载 userService 时,它会获得存根函数。
var registerStub = sinon.stub(userService,"registerUser")
registerStub.callsFake(()=>
return Promise.resolve(userResponse)
)
这是一个使用存根方法的用户路由测试示例
user.routes.test.js
const superTest = require('supertest')
const expect = require('chai').expect
const assert = require("chai").assert
const sinon = require("sinon")
const userService = require("../services/user.services")
const app = require("../app').app
describe("User Routes: ",function()
var registerStub = sinon.stub(userService,"registerUser")
beforeEach(()=>
registerStub.callsFake(()=>
return Promise.resolve(userResponse)
)
)
afterEach(()=>
registerStub.restore()
)
it("can get register a user",function(done)
superTest(app).post("/user/register")
.set("XXXXXXX")
.send("email=sfsfsf&firstName=xsdfsfs")
.expect(200).then( response =>
assert(response.text == EXPECTED_RESPONSE)
done()
).catch(e=>
//TEST FAILED
console.log(e)
done(e)
)
)
)
上面的测试是确保用户路由充当 URL“/user/register”上的事件处理程序的绊脚石。它不是对 userService 内部逻辑的测试。在此设计中,UserRoute 和 UserService 有 2 个独立的职责。
UserService 的责任
UserService.register 返回一个promise,该promise 解析为一个响应对象或拒绝一个错误对象。如果 promise 被拒绝,则另一个实用程序类负责将该结果转换为状态代码 500,如果 promise 已解决,则将其转换为 JSON。可以使用早期的存根方法来测试下面的代码,以模拟您的数据库客户端/模块,使其看起来像用户已经存在,然后确认返回了适当的消息。
user.service.js
module.exports.register = function(req)
var resolve;
var reject;
var promise = new Promise((resolveTemp,rejectTemp) =>
resolve = resolveTemp
reject = rejectTemp
)
let newUser = <RegisterUserDTO>req.body;
getByUsername(newUser.username)
.then((user:User) =>
if(!user)
getByEmail(newUser.email).then(user =>
if(!user)
insertUser(newUser)
.then((result) =>
resolve(
success: true,
msg: "User registered",
userId: result.insertId
);
)
.catch(err =>
reject(err);
);
else
resolve( success: false, msg: "This email already exists" );
);
else
resolve( success: false, msg: "This username already exists" );
)
return promise
您可以通过将操作结果包装为 Promise 来分离用户服务对请求和响应的了解需求。对我来说,错误对象会导致一切中止,因此 reject(err) 通常就足够了,但您可以使用 reject(message:"ssfsfs",code:500,error:err)
或创建用于所有拒绝的 ApplicationError
类型。这是一种让错误从业务逻辑冒泡到 HTTP 层的巧妙方法,而无需将 Request 和 Response 对象深入传递到您的业务逻辑中。
【讨论】:
以上是关于Node.js 与 Typescript 和 Mysql 测试路由模拟服务的主要内容,如果未能解决你的问题,请参考以下文章
Visual Studio 与 WebStorm 中的 Typescript 编译 - 两者都使用 Node.js 进行编译吗?
Typescript / Node.js - 如何模拟集成测试的传递依赖项?
Node.js、TypeScript、JavaScript 和 Angular 之间的连接 [关闭]