使用 TypeScript 在 Jest 中模拟 ES6 class 方法

Mocking ES6 class method in Jest with TypeScript

过去一周我一直在为此苦苦挣扎,所以我终于不得不承认我不知道为什么会这样。

我正在使用带有 express 和 TypeScript 的超级测试来测试我的一些 API 路由的实现。请记住,我正在主动打开数据库连接并在此处写入数据库,而不是对这些方法使用模拟。

但是,我的一些控制器依赖于连接到每个控制器的记录器实例(这只是一个 ES6 class)。

我想弄清楚为什么它不能用下面的代码正确地模拟它:

utils/logger.ts:

class Logger {
  private filename: string

  public constructor(filename: string) {
    this.filename = filename
  }

  public debug(message: string) {
    console.log('debug:', message)
  }
}

controllers/user.ts

class UserController {
  public logger: any

  constructor() {
    this.logger = new Logger('UserController.ts')
  }

  async create(req: Request, res: Response) {
    this.logger.debug('creating') <-- THIS KEEPS ERRORING with: TypeError: Cannot read property 'logger' of undefined
    return res.status(200).end()
  }
}

export default new UserController()

server/routes/user.ts:

import users from '../controllers/user'

class UserRoutes {
  constructor(server: any) {
    server.post('/api/v1/users/create', users.create)
  }
}

export default UserRoutes

这是我要传递给 supertest 的应用服务器: server.ts:

import bodyParser from 'body-parser'
import express from 'express'
import database from '../server/db'
import Routes from '../server/routes'

class TestApp {
  public server: any
  public database: any

  constructor() {
    this.initDatabase()
    this.run()
  }

  public async run(): Promise<void> {
    this.server = express()
    this.middleware()
    this.routes()
  }

  private middleware(): void {
    this.server.use(bodyParser.json())
  }

 private routes(): void {
    new Routes(this.server)
  }

  public initDatabase(): void {
    this.database = new database()
  }

  public closeDatabase(): void {
    this.database.close()
  }
}

export default TestApp

最后是实际测试:

import express, { Application } from 'express'
import request from 'supertest'
import TestApp from './server'
let server: any

const initServer = () => {
  jest.mock('../server/controllers/user', () => {
    return jest.fn().mockImplementation(() => {
      return {
        logger: {
          debug: jest.fn()
        }
      }
    })
  })

  const testApp = new TestApp()
  const server: Application = testApp.server
  const exp = express()
  return exp.use(server)
}

beforeAll(async () => {
  server = initServer()
})

describe('POST /api/v1/users', () => {
  test('should create a new user', async () => {
    const res = await request(server)
      .post('/api/v1/users/create')
      .send({
        username: 'testing'
      })
      expect(res.status).toEqual(200)
  })
})

我已经尝试在用户控制器中模拟 logger 的实例以及实际的 logger.ts 整个 class,但到目前为止没有任何效果。这是完整的堆栈跟踪:

  TypeError: Cannot read property 'logger' of undefined

  46 |     
> 47 |     this.logger.debug('creating user')
     |          ^
  48 |
  49 |     try {
  50 |       const user = await User.create({

  at src/server/controllers/user.ts:47:10
  at src/server/controllers/user.ts:8:71
  at __awaiter (src/server/controllers/user.ts:4:12)
  at create (src/server/controllers/user.ts:50:16)
  at Layer.handle [as handle_request] (node_modules/express/lib/router/layer.js:95:5)
  at next (node_modules/express/lib/router/route.js:137:13)
  at Route.dispatch (node_modules/express/lib/router/route.js:112:3)
  at Layer.handle [as handle_request] (node_modules/express/lib/router/layer.js:95:5)
  at node_modules/express/lib/router/index.js:281:22
  at Function.process_params (node_modules/express/lib/router/index.js:335:12)
  at next (node_modules/express/lib/router/index.js:275:10)
  at SessionStrategy.strategy.pass (node_modules/passport/lib/middleware/authenticate.js:343:9)
  at SessionStrategy.authenticate (node_modules/passport/lib/strategies/session.js:75:10)
  at attempt (node_modules/passport/lib/middleware/authenticate.js:366:16)
  at authenticate (node_modules/passport/lib/middleware/authenticate.js:367:7)
  at Layer.handle [as handle_request] (node_modules/express/lib/router/layer.js:95:5)
  at trim_prefix (node_modules/express/lib/router/index.js:317:13)
  at node_modules/express/lib/router/index.js:284:7
  at Function.process_params (node_modules/express/lib/router/index.js:335:12)
  at next (node_modules/express/lib/router/index.js:275:10)

  console.log src/server/controllers/user.ts:46

为什么 this(用户控制器实例)总是 undefined

这里你失去了上下文:

server/routes/user.ts

server.post('/api/v1/users/create', users.create)

有几个选项可以避免它

  1. 在控制器中将方法定义为 属性
controllers/user.ts

class UserController {
  // ...
  create = async (req: Request, res: Response) => {
    this.logger.debug('creating');
    return res.status(200).end();
  }
  // ...
}
  1. 将方法绑定到实例(它又创建了一个函数)
server/routes/user.ts

class UserRoutes {
  constructor(server: any) {
    server.post('/api/v1/users/create', users.create.bind(users));
  }
}

  1. 几乎与上一个相同,但使用箭头函数而不是 bind
server/routes/user.ts

class UserRoutes {
  constructor(server: any) {
    server.post('/api/v1/users/create', (req, res) => users.create(req, res));
  }
}