如何为 JWT 策略编写单元测试用例

Posted

技术标签:

【中文标题】如何为 JWT 策略编写单元测试用例【英文标题】:How to write unit test case for JWT strategy 【发布时间】:2020-09-19 09:05:55 【问题描述】:

我是新来的 passport.js,并试图涵盖我的 JWT 策略的单元测试用例。任何人都可以建议如何做到这一点?

// Setup JWT strategy for all requests
passport.use(
  new JWTStrategy(
    
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: JWT_PRIVATE_KEY,
    ,
    async (jwtPayload: any, done: any) => 
      const isUser = jwtPayload.type === EntityType.User;
      const model = isUser ? userModel : vendorModel;
      try 
        const document = await model.findOne( _id: jwtPayload.id );
        if (document) 
          return done(null, jwtPayload);
         else 
          return done(null, false);
        
       catch (err) 
        return done(err, false);
      
    ,
  ),
);

【问题讨论】:

【参考方案1】:

使用supertest验证全周期


import request from 'supertest';
import express from 'express';
import jwt from 'jsonwebtoken'


export const createAuthToken = (userId) => 
  const body =  
    type: EntityType.User,
    id: userId,
  ;
  return jwt.sign(body, JWT_PRIVATE_KEY);
;

// this function should configure express app
const appLoader = async app => 
  (await import('../app/loaders/express')).expressLoader( app ); // express bindings and routes
  await import('./'); // passport config


describe('passport-jwt auth', () => 
  const app = express();
  const token = createAuthToken('user1')


  beforeAll(async () => 
    await appLoader( app );
  );

  it('should verify auth', async () => 
    jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('mocked user document');


    await request(app)
      .get('/protected-endpoint')
      .set('Authorization', `Bearer $token`)
      .expect(200);
  );

  it('should verify auth - failure', async () => 
    await request(app)
      .get('/protected-endpoint')
      .set('Authorization', `Bearer wrong-token`)
      .expect(401);
  );
);

【讨论】:

【参考方案2】:

单元测试解决方案:

index.ts:

import passport from 'passport';
import  Strategy as JWTStrategy, ExtractJwt  from 'passport-jwt';
import  userModel, vendorModel, EntityType  from './models';

const JWT_PRIVATE_KEY = 'secret 123';

passport.use(
  new JWTStrategy(
    
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: JWT_PRIVATE_KEY,
    ,
    async (jwtPayload: any, done: any) => 
      console.log('123123');
      const isUser = jwtPayload.type === EntityType.User;
      const model = isUser ? userModel : vendorModel;
      try 
        const document = await model.findOne( _id: jwtPayload.id );
        if (document) 
          return done(null, jwtPayload);
         else 
          return done(null, false);
        
       catch (err) 
        return done(err, false);
      
    ,
  ),
);

models.ts:

export enum EntityType 
  User = 'User',

export const userModel = 
  async findOne(opts) 
    return 'real user document';
  ,
;
export const vendorModel = 
  async findOne(opts) 
    return 'real vendor document';
  ,
;

index.test.ts:

import  Strategy as JWTStrategy, ExtractJwt, VerifyCallback, StrategyOptions  from 'passport-jwt';
import passport from 'passport';
import  userModel, vendorModel  from './models';

jest.mock('passport-jwt', () => 
  const mJWTStrategy = jest.fn();
  const mExtractJwt = 
    fromAuthHeaderAsBearerToken: jest.fn(),
  ;
  return  Strategy: mJWTStrategy, ExtractJwt: mExtractJwt ;
);
jest.mock('passport', () => 
  return  use: jest.fn() ;
);

describe('62125872', () => 
  let verifyRef;
  beforeEach(() => 
    const mJwtFromRequestFunction = jest.fn();
    (ExtractJwt.fromAuthHeaderAsBearerToken as jest.MockedFunction<
      typeof ExtractJwt.fromAuthHeaderAsBearerToken
    >).mockReturnValueOnce(mJwtFromRequestFunction);

    (JWTStrategy as jest.MockedClass<any>).mockImplementation((opt: StrategyOptions, verify: VerifyCallback) => 
      verifyRef = verify;
    );
  );

  it('should verify using user model and call done with jwtpayload if user document exists', async () => 
    const payload =  type: 'User', id: 1 ;
    const mDone = jest.fn();

    jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('mocked user document');
    await import('./');
    await verifyRef(payload, mDone);
    expect(passport.use).toBeCalledWith(expect.any(Object));
    expect(JWTStrategy).toBeCalledWith(
       jwtFromRequest: expect.any(Function), secretOrKey: 'secret 123' ,
      expect.any(Function),
    );
    expect(ExtractJwt.fromAuthHeaderAsBearerToken).toBeCalledTimes(1);
    expect(userModel.findOne).toBeCalledWith( _id: 1 );
    expect(mDone).toBeCalledWith(null,  type: 'User', id: 1 );
  );

  it("should verify using user model and call done with false if user document doesn't exist", async () => 
    const payload =  type: 'User', id: 1 ;
    const mDone = jest.fn();

    jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('');
    await import('./');
    await verifyRef(payload, mDone);
    expect(passport.use).toBeCalledWith(expect.any(Object));
    expect(JWTStrategy).toBeCalledWith(
       jwtFromRequest: expect.any(Function), secretOrKey: 'secret 123' ,
      expect.any(Function),
    );
    expect(ExtractJwt.fromAuthHeaderAsBearerToken).toBeCalledTimes(1);
    expect(userModel.findOne).toBeCalledWith( _id: 1 );
    expect(mDone).toBeCalledWith(null, false);
  );

  // you can do the rest parts
);

单元测试结果:

 PASS  ***/62125872/index.test.ts
  62125872
    ✓ should verify using user model and call done with jwtpayload if user document exists (11ms)
    ✓ should verify using user model and call done with false if user document doesn't exist (2ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |      85 |    83.33 |      60 |   84.21 |                   
 index.ts  |   92.86 |       75 |     100 |   92.31 | 24                
 models.ts |   66.67 |      100 |   33.33 |   66.67 | 6,11              
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.716s, estimated 10s

【讨论】:

以上是关于如何为 JWT 策略编写单元测试用例的主要内容,如果未能解决你的问题,请参考以下文章

我们如何为嵌套函数编写单元测试用例(Jasmine)?

Spring Boot:如何为删除其余模板编写单元测试用例

如何在 jasmine 中编写单元测试用例?

如何使用 Kafka Streams 为应用程序编写单元测试用例

用于 RowMapper 的 Spring jdbc 模板的单元测试用例

如何为使用两个数据库(mysql和mongo)的django项目编写单元测试