Testing

Testing authentication is annoying. You need valid tokens, but you don't want to create real users or hit AWS in every test.

The testing package solves this. It can generate fake tokens for fast tests, or create real Cognito users for E2E tests.

Installation

pnpm add --save-dev @nestjs-cognito/testing      # NestJS Cognito testing utilities
pnpm add --save-dev @nestjs/testing              # NestJS testing utilities
pnpm add --save-dev jest                         # Test runner
pnpm add --save-dev @types/jest                  # TypeScript definitions

Real E2E tests with Cognito

This creates real users in AWS Cognito. Use this to test your authentication flow end-to-end.

import { handler, request, spec } from "pactum";
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';

describe('Auth Tests', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleRef.createNestApplication();
    await app.init();

    // Configure Pactum to use the NestJS application
    request.setBaseUrl(`http://localhost:${process.env.PORT || 3000}`);
  });

  afterAll(async () => {
    await app.close();
  });
});

Setup:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CognitoTestingModule } from '@nestjs-cognito/testing';
import { CognitoAuthModule } from '@nestjs-cognito/auth';

@Module({
  imports: [
    // Configure auth module with environment variables
    CognitoAuthModule.registerAsync({
      imports: [ConfigModule.forRoot()],
      useFactory: (config: ConfigService) => ({
        jwtVerifier: {
          userPoolId: config.get('COGNITO_USER_POOL_ID'),
          clientId: config.get('COGNITO_CLIENT_ID'),
          tokenUse: 'id',
        },
      }),
      inject: [ConfigService],
    }),
    // Configure testing module with AWS credentials
    CognitoTestingModule.registerAsync({
      imports: [ConfigModule.forRoot()],
      useFactory: (config: ConfigService) => ({
        identityProvider: {
          region: config.get('COGNITO_REGION'),
        },
      }),
      inject: [ConfigService],
    }),
  ],
})
export class TestModule {}

Fast tests with mocks

Skip AWS completely. The mock mode generates fake tokens that pass verification.

import { COGNITO_JWT_VERIFIER_INSTANCE_TOKEN } from '@nestjs-cognito/core';
import { CognitoTestingModule } from '@nestjs-cognito/testing';
import { Test } from "@nestjs/testing";
import { handler, request, spec } from "pactum";

describe('Auth Tests', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [
        CognitoTestingModule.register({}, {
          enabled: true, // Enable mock mode
          user: {
            username: 'test-user',
            email: 'test@example.com',
            groups: ['users'],
          },
        }),
        AppModule,
      ],
    })
      .overrideProvider(COGNITO_JWT_VERIFIER_INSTANCE_TOKEN)
      .useFactory({
        factory: CognitoTestingModule.createJwtVerifierFactory
      })
      .compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });
});

Test protected routes

// Get a fake token
const response = await request(app.getHttpServer())
  .post('/cognito-testing-login')
  .send({
    username: 'test-user',
    password: 'any-password',  // Not validated in mock mode
    clientId: 'mock-client-id'
  });

// Use it to access protected routes
await request(app.getHttpServer())
  .get('/protected')
  .set('Authorization', `Bearer ${response.body.AccessToken}`)
  .expect(200);

Test authorization

// Configure mock user with specific groups
await request(app.getHttpServer())
  .post('/config')
  .send({
    enabled: true,
    user: {
      username: 'admin-user',
      email: 'admin@example.com',
      groups: ['admin'],
    },
  });

// Login
const loginResponse = await request(app.getHttpServer())
  .post('/cognito-testing-login')
  .send({
    username: 'admin@example.com',
    password: 'password',
    clientId: 'test-client',
  });

    // Test protected route access
    await request(app.getHttpServer())
      .get('/admin/dashboard')
      .set('Authorization', `Bearer ${loginResponse.body.AccessToken}`)
      .expect(200);
  });
});

Advanced Testing

Dynamic User Configuration

Update mock settings during test execution:

await request(app.getHttpServer())
  .post('/config')
  .send({
    user: {
      username: 'new-user',
      email: 'new@example.com',
      groups: ['admin'],
    },
  })
  .expect(200);

Testing Different Token Types

describe('Token Type Tests', () => {
  it('should validate ID tokens', async () => {
    const response = await request(app.getHttpServer())
      .post('/cognito-testing-login')
      .send({
        username: 'test@example.com',
        password: 'password',
        clientId: 'test-client',
      })
      .expect(200);

    await request(app.getHttpServer())
      .get('/protected')
      .set('Authorization', `Bearer ${response.body.IdToken}`)
      .expect(200);
  });

  it('should validate access tokens', async () => {
    const response = await request(app.getHttpServer())
      .post('/cognito-testing-login')
      .send({
        username: 'test@example.com',
        password: 'password',
        clientId: 'test-client',
      })
      .expect(200);

    await request(app.getHttpServer())
      .get('/protected')
      .set('Authorization', `Bearer ${response.body.AccessToken}`)
      .expect(200);
  });
});