带有typescript和mysql测试路由的node.js模拟服务

kqqjbcuj  于 2021-06-21  发布在  Mysql
关注(0)|答案(1)|浏览(295)

我正在使用node.js和typescript来构建一个webapi。对于数据库,我使用mariadb(mysql)。我已经使用repository模式+工作单元构建了这个项目(我不是.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并测试此路由?这是使用node的“错误”方法吗?
提前谢谢

guz6ccqo

guz6ccqo1#

为了更容易测试,我会将更多的逻辑引入到您的服务中,并划分路由和服务的责任。在路由中有很多if语句,甚至创建了一个存储库,这将使测试变得非常困难,并赋予路由比路由请求和响应更大的责任。
这里有一个方法来构造它。
用户.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)

用户路由的责任
如果分析上面的代码,路由的责任不再是确定适当的响应消息,检查用户是否存在,等等。它的工作是从userservice获取响应并将其发送给用户。这简化了我需要测试的内容。因为在路由测试中,我只关心路由中立即发生的事情,所以我可以完全删除userservice.register。userservice返回的是promise resolve还是promise rejection并不重要,只是路由正确地处理了这些场景。
上面的代码和url“/user/register”可以很容易地用sinon.js进行测试。节点只加载一次“required”模块,因此在用户路由的单元测试中,您可以加载userservice,向register函数添加存根,然后当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)
      })
  })
})

上面的测试是一个trip wire,确保用户路由充当url“/user/register”上的事件处理程序。它不是对userservice内部逻辑的测试。在这个设计中,userroute和userservice有两个独立的职责。
用户服务的责任
register返回解析为响应对象或用错误对象拒绝的承诺。另一个实用程序类负责将结果转换为状态代码500(如果承诺被拒绝),或者转换为json(如果承诺被解析)。测试下面的代码可以使用前面的stubing方法来模拟数据库客户机/模块,使其看起来像用户已经存在,然后确认返回了相应的消息。
用户.serivice.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
}

通过将操作结果 Package 为承诺,可以分离用户服务了解请求和响应的需求。对我来说,一个error对象会导致所有操作都中止,所以reject(err)通常就足够了,但是您可以使用 reject({message:"ssfsfs",code:500,error:err}) 或创建一个 ApplicationError 您用于所有拒绝的类型。这是一种让错误从业务逻辑冒泡到http层的好方法,而不必将请求和响应对象深入到业务逻辑中。

相关问题