用 Jest 模拟 jsonwebtoken 模块

Posted

技术标签:

【中文标题】用 Jest 模拟 jsonwebtoken 模块【英文标题】:Mocking jsonwebtoken module with Jest 【发布时间】:2021-02-04 20:43:49 【问题描述】:

我尝试用玩笑来模拟 npm 模块 jsonwebtoken 的验证功能。该函数返回一个解码的令牌,但我想将此函数的自定义返回传递给我的单元测试。

在继续请求之前,我提出了检查访问令牌有效性的明确请求。但我想模拟令牌检查的时刻以直接返回用户值。并轻松通过这一步。我把代码的关注部分放在了你的位置上。

但是打字稿给我发了这个错误: 类型'(令牌:字符串,secretOrPublicKey:秘密,选项?:验证选项|未定义)上不存在属性'mockReturnValue':字符串|目的; (token: string, secretOrPublicKey: string | Buffer | key: string | Buffer; passphrase: string; | GetPublicKeyOrSecret, callback?: VerifyCallback | undefined): void; (令牌:字符串,secretOrPublicKey:字符串| ... ...'。

所以模拟不起作用,我不明白。我遵循 Jest.io 上的模拟 axios 步骤,但它似乎不适用于 jsonwebtoken。

大家知道问题出在哪里,或者如何在玩笑时模拟这个 jsonwebtoken 模块吗?

users.test.ts

import jwt from 'jsonwebtoken'
    jest.mock('jwt')
    jwt.verify.mockReturnValue(
                    userId: String(member._id),
                    email: String(member.email),
                    permissionLevel: member.permissionLevel,
                    username: String(member.username),
                )

describe('### /GET users', () => 
            it('it should return 200 (Users List)', async (done) => 
                const res = await request(app).set('Authorization', 'Bearer').get('/users')
                expect(res.status).toBe(200)
            )
)

验证.ts

public isAccessTokenValid = (req: Request, res: Response, next: NextFunction): void => 
        if (req.cryptedAccessToken) 
            try 
                req.accessToken = jwt.verify(req.cryptedAccessToken, ACCESS_TOKEN_SECRET)
                next()
             catch (err) 
                res.status(498).send( error: err.message )
            
         else res.status(401).send( error: 'cryptedAccessToken field not present in request' )
    

最好的问候

【问题讨论】:

【参考方案1】:

那是因为 TypeScript“不知道”你已经模拟了模块,所以你可以尝试类型转换正确的类型

import jwt from 'jsonwebtoken'
const  verify  = jwt as jest.Mocked<typeof import('jsonwebtoken')>

verify.mockReturnValue(
  //...
)

(jwt as jest.Mocked<typeof import('jsonwebtoken')>).verify.mockReturnValue(
  //...
)

【讨论】:

未测试,但应该足够接近 您好,感谢您的快速回答。我测试了您的解决方案,实际上它解决了我的问题,但我们不想在 mockReturnValue 的参数中获取对象。错误消息说他等待无效响应,但我认为我可以调整返回类型? const verify = jwt as jest.Mocked&lt;typeof import('jsonwebtoken')&gt; verify.mockReturnValue( userId: member._id ) 类型参数 ' userId: Schema.Types.ObjectId; ' 不可分配给“void”类型的参数 verify方法有多种实现,其中一种返回void。 ts 无法找出您要使用的 impl。尝试使用const verify = jwt.verify as jest.MockedFunction&lt;( token: string, secretOrPublicKey: jwt.Secret, options?: jwt.VerifyOptions, ) =&gt; object | string&gt;;【参考方案2】:

我花了一段时间,但我得到了这个工作,有几节课。

    需要使用jest.mock('jsonwebtoken'),并且必须放在spec文件的顶部,紧跟import jwt from 'jsonwebtoken'之后

    这样做会模拟 jwt 的所有功能。就我而言,我只是想模拟验证。这段代码解决了这个问题(替换上面的 (1))。

     import jwt from 'jsonwebtoken;
     jest.mock('jsonwebtoken', () => (
     ...jest.requireActual('jsonwebtoken'), // import and retain the original functionalities
     verify: jest.fn().mockReturnValue( foo: 'bar' ), // overwrite verify
     ));
    

    然后我可以通过以下三种方式之一模拟验证:

a) 将其保留为默认模拟(返回 foo bar)

b) 使用 mockReturnValue,但我需要指定要替换的重载(感谢 @Gabriel)

  const verify = jwt.verify as jest.MockedFunction<
    (
      token: string,
      secretOrPublicKey: jwt.Secret,
      options?: jwt.VerifyOptions,
    ) => Record<string, unknown> | string
  >;
  verify.mockReturnValue( verified: 'true' );

c) 使用模拟实现

 const verify = jest.spyOn(jwt, 'verify');
 verify.mockImplementation(() => () => ( verified: 'true' ));

所有这些令人讨厌的事情是,现在对规范文件中所有描述/它的验证都进行了模拟。

【讨论】:

如果需要,您可以使用 mockRestore() 函数从 jwt.verify 函数中删除模拟。请注意,这将在您调用该函数后删除规范文件中所有内容的模拟实现。见jest docs【参考方案3】:

使用verify同时返回解码后的token时:

jest.mock('jsonwebtoken', () => (
  verify: jest.fn((token, secretOrPublicKey, options, callback) => 
    return callback(null, sub: 'user_id');
  )
));

【讨论】:

以上是关于用 Jest 模拟 jsonwebtoken 模块的主要内容,如果未能解决你的问题,请参考以下文章

用 Jest 模拟基于承诺的请求

Jest - 在另一个模块函数中模拟函数

跨测试重用 Jest 模块模拟

使用 Jest 模拟的服务导致“不允许 jest.mock() 的模块工厂引用任何超出范围的变量”错误

使用 Jest 模拟请求标头模块

开玩笑 - 用“模拟”前缀模拟不起作用