Skip to main content

Auth0 Actions Unit Test

The Actions NPM (@auth0/actions) package enables the use of TypeScript development on external projects, allowing developers to follow best practices and improve their unit testing based on the TypeScript definitions.

How it works

Follow the installation and import guidelines described at: How Actions NPM works. To unit test an Action, you must mock the event and api objects that your Action function receives. You can create these mocks using the TypeScript definitions included in auth0/actions, which ensures your tests accurately reflect the production environment. Testing frameworks like Jest are ideal for managing mocking and functionality. The Unit Tests can be run in a local environment, version control, or CI/CD process, improving the overall quality assurance and validations before impacting changes on Auth0 Tenants.

Examples

The following examples provide validation for a series of scenarios by mocking the necessary objects.
The examples use Jest (https://www.npmjs.com/package/jest), but any testing library can be used.

Configuration

In your package.json, define any development dependencies to have IntelliSense help when writing your Action.
  • Javascript
  • TypeScript
{
  "name": "actions-js",
  "version": "1.0.0",
  "description": "Actions JS",
  "main": "example.js",
  "scripts": {
    "test": "jest"
  },
  "author": "John Doe",
  "license": "ISC",
  "devDependencies": {
    "@auth0/actions": "^0.7.1",
    "jest": "^29.7.0"
  }
}

Pre-user registration access control and user metadata setup

The following example Action checks if the user email has a forbidden email domain, and calls api.access.deny() when matching. Otherwise, it checks if the full name has being provided through Custom Prompts additional fields and proceeds to set the full name at the user profile user_metadata, otherwise sends a validation error to Universal Login.
  • Javascript
  • TypeScript
/** @import {Event, PreUserRegistrationAPI} from "@auth0/actions/pre-user-registration/v2" */

/**
* Handler that will be called during the execution of a PreUserRegistration flow.
*
* @param {Event} event - Details about the context and user that is attempting to register.
* @param {PreUserRegistrationAPI} api - Interface whose methods can be used to change the behavior of the signup.
*/
exports.onExecutePreUserRegistration = async (event, api) => {
  const user = event.user;

  if (user.email?.endsWith('@example.com')) {
    api.access.deny('forbidden', 'Forbidden email domain')
    return;
  }

  const fullName = event.request.body['ulp-fullName'];

  if (fullName === undefined) {
    api.validation.error('invalid_payload', 'Missing full name');
    return;
  }

  api.user.setUserMetadata('full_name', fullName);
}
The Unit Test performs some validations to maximize code coverage, by mocking event and api objects.
  • Javascript
  • TypeScript
const { onExecutePreUserRegistration } = require('./preUserRegistration');

describe('onExecutePreUserRegistration', () => {
  const mockApi = {
    access: {
      deny: jest.fn(),
    },
    user: {
      setUserMetadata: jest.fn(),
    },
    validation: {
      error: jest.fn(),
    },
  };

  beforeEach(() => {
    jest.resetAllMocks();
  });

  afterEach(() => {
    jest.resetAllMocks();
  });

  it('forbids email domain', async () => {
    const mockEvent = {
      user: {
        email: 'johndoe@example.com',
      }
    };

    await onExecutePreUserRegistration(mockEvent, mockApi);

    expect(mockApi.access.deny).toHaveBeenCalledWith('forbidden', 'Forbidden email domain');
    expect(mockApi.validation.error).not.toHaveBeenCalled();
    expect(mockApi.user.setUserMetadata).not.toHaveBeenCalled();
  });

  it('allows email domain without full name', async () => {
    const mockEvent = {
      request: {
        body: {},
      },
      user: {
        email: 'johndoe@test.com',
      },
    };

    await onExecutePreUserRegistration(mockEvent, mockApi);

    expect(mockApi.access.deny).not.toHaveBeenCalled();
    expect(mockApi.validation.error).toHaveBeenCalledWith('invalid_payload', 'Missing full name');
    expect(mockApi.user.setUserMetadata).not.toHaveBeenCalled();
  });

  it('allows email domain with full name', async () => {
    const mockEvent = {
      request: {
        body: {
          'ulp-fullName': 'John Doe'
        },
      },
      user: {
        email: 'johndoe@test.com',
      },
    };

    await onExecutePreUserRegistration(mockEvent, mockApi);

    expect(mockApi.access.deny).not.toHaveBeenCalled();
    expect(mockApi.validation.error).not.toHaveBeenCalled();
    expect(mockApi.user.setUserMetadata).toHaveBeenCalledWith('full_name', 'John Doe');
  });
});

Custom email provider and HTTP request

The following example Action attempts to send a message via HTTP request to an external service, handling the potential request error to drop the notification. It uses secrets for the external service URL and authorization API Key.
  • Javascript
  • TypeScript
/** @import {Event, CustomEmailProviderAPI} from "@auth0/actions/custom-email-provider/v1" */

/**
* Handler to be executed while sending an email notification
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {CustomEmailProviderAPI} api - Methods and utilities to help change the behavior of sending a email notification.
*/
exports.onExecuteCustomEmailProvider = async (event, api) => {
  const notification = event.notification;
  const message = {
    body: notification.html
  };

  try {
    await fetch(event.secrets.URL, {
      method: 'POST',
      headers: {
        'X-API-Key': event.secrets.API_KEY,
      },
      body: JSON.stringify(message),
    });
  } catch (err) {
    api.notification.drop('External service failure');
  }
}

The Unit Test performs some validations to maximize code coverage, by mocking event and api objects, in addition to the fetch function.
  • Javascript
  • TypeScript
const { onExecuteCustomEmailProvider } = require('./customEmailProvider');

describe('onExecuteCustomEmailProvider', () => {
  const mockApi = {
    notification: {
      drop: jest.fn(),
    },
  };

  const mockEvent = {
    notification: {
      html: '<h1>Hello world</h1>',
    },
    secrets: {
      URL: 'https://example.com/service',
      API_KEY: 'ApiKeySecret1234.',
    },
    user: {
      email: 'johndoe@example.com',
    },
  };

  beforeEach(() => {
    jest.resetAllMocks();
  });

  afterEach(() => {
    jest.resetAllMocks();
  });

  it('succeeds on external service request', async () => {
    jest.spyOn(global, 'fetch').mockImplementationOnce(() => Promise.resolve({
      ok: true,
      status: 200,
      json: async () => ({ message: 'Success!' }),
    }));

    await onExecuteCustomEmailProvider(mockEvent, mockApi);

    expect(global.fetch).toHaveBeenCalled();
    expect(mockApi.notification.drop).not.toHaveBeenCalled();
  });

  it('fails on external service request', async () => {
    jest.spyOn(global, 'fetch').mockImplementationOnce(() => Promise.reject({
      ok: false,
      status: 500,
      json: async () => ({ error: 'Server Error' }),
    }));

    await onExecuteCustomEmailProvider(mockEvent, mockApi);

    expect(mockApi.notification.drop).toHaveBeenCalledWith('External service failure');
  });
});

To learn more about @auth0/actions, visit: https://www.npmjs.com/package/@auth0/actions. To learn more about writing Actions, read Write Your First Action.