Насмешливый метод класса ES6 в Jest с использованием TypeScript - PullRequest
1 голос
/ 26 марта 2020

Я боролся с этим на прошлой неделе, поэтому мне, наконец, нужно признать, что я не знаю, почему это происходит.

Я использую супертест с express и TypeScript для проверить реализацию некоторых из моих маршрутов API. Имейте в виду, я активно открываю соединение с БД и пишу в БД здесь, а не использую mocks для этих методов.

Однако у меня есть зависимость в некоторых из моих контроллеров, которые полагаются на экземпляр регистратора подключен к каждому контроллеру (который является просто классом ES6).

Я пытаюсь выяснить, почему он не издевается должным образом, с помощью кода ниже:

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 весь класс, но пока ничего не получалось. Вот полная трассировка стека:

  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?

1 Ответ

1 голос
/ 27 марта 2020

Здесь вы потеряете контекст:

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();
  }
  // ...
}
Привязать метод к экземпляру (он создает еще одну функцию)
server/routes/user.ts

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

Почти такой же, как предыдущий, но с функцией стрелки вместо bind
server/routes/user.ts

class UserRoutes {
  constructor(server: any) {
    server.post('/api/v1/users/create', (req, res) => users.create(req, res));
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...