使用 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)
有几个选项可以避免它
- 在控制器中将方法定义为 属性
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));
}
}
过去一周我一直在为此苦苦挣扎,所以我终于不得不承认我不知道为什么会这样。
我正在使用带有 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)
有几个选项可以避免它
- 在控制器中将方法定义为 属性
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));
}
}