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 进行编译吗?

nestjs为啥不火

Typescript / Node.js - 如何模拟集成测试的传递依赖项?

Node.js、TypeScript、JavaScript 和 Angular 之间的连接 [关闭]

Node.js 和 sequelize-typescript - 数据访问对象和业务对象

在 Node.js 和 Vue.js 之间共享 TypeScript 代码