---
schema_version: "1"
id: 2ba2f8f37f7918961ad2da6cf9581b4fc11ac2b1d98acb86c56b3b1945ebdc93
title: Writing Integration Tests
collection: mulesoft
version: current
locale: en-us
portal_url: https://docs.salesforce.com/mulesoft/current/en-us/pdk/1.8/policies-pdk-integration-tests.md
canonical_url: https://docs.mulesoft.com/pdk/1.8/policies-pdk-integration-tests
source_site: docs.mulesoft.com
content_type: documentation
content_hash: a192e67d2c5bff66ffb42338f56b612df161ee5c9776f000d814865039f6fb7f
description: MuleSoft Documentation Site
release: "1.8"
latest: false
---
---
product: Omni Gateway Policy Development Kit (PDK)
version: 1.8
---

# Writing Integration Tests

> For the full documentation index, see: https://docs.mulesoft.com/llms.txt

Omni Gateway Policy Development Kit (PDK) provides an automated testing framework to detect
integration regressions in your custom policy.

The integration testing framework provided by PDK only supports Omni Gateway running in Local Mode
in a Docker container. If you plan to deploy or policy to Omni Gateway running in Connected Mode or
to a different platform, you should still first test your policy in PDK by using Omni Gateway
running in Local Mode in a Docker container.

> [!IMPORTANT] You can run Omni Gateway in Local Mode on Docker in Windows only to develop and test
> your policies. However, Omni Gateway does not support Windows in production environments.

To test your policy, complete the following tasks:

1.  Review [Before You Begin](#before-you-begin).

2.  Review [Integration Tests Directory Structure](#structure).

3.  [Register an Omni Gateway Instance in Local Mode](#register).

4.  [Configure Your Custom Policy](#configure-your-custom-policy).

5.  [Create a Test Function](#create-a-test-function).

6.  [Run an Integration Test](#run-integration-test).

7.  [Configure Services for Integration Testing](#configure-services-for-integration-testing).

8.  [Review Service Logs](#review-service-logs).

## Before You Begin

Ensure that you have:

- [Installed Docker](https://www.docker.com/)

## Integration Tests Directory Structure

By default, the `<root-directory>/tests` directory contains the following files:

tests ├── requests.rs └── common └── mod.rs └── config └── logging.yaml └── registration.yaml

The `/tests` directory contains the integration tests. By default, the folder contains:

- `requests.rs` file: An individual test module and executable.

- `common/mod.rs` file: A test module that contains functionalities, API configurations, and policy
  configurations that must be included in every integration test module.

## Register an Omni Gateway Instance in Local Mode

To begin using the `make test` command to debug your custom policy, register an Omni Gateway in
Local Mode.

For the `make test` command to work, an Omni Gateway `registration.yaml` file must exist in the
`<root-directory>/tests/config` directory. Create this file by running the registration command in
the directory, or move a `registration.yaml` file of a previously registered Omni Gateway to the
directory.

To register Omni Gateway, see:

- [Register and Run with a Username and Password in a Docker Container](https://docs.mulesoft.com/gateway/latest/local-reg-run-up#docker)

- [Register and Run with a Connected App in a Docker Container](https://docs.mulesoft.com/gateway/latest/local-reg-run-app#docker)

- [Register and Run with a Token in a Docker Container](https://docs.mulesoft.com/gateway/latest/local-reg-run-token#docker)

> [!IMPORTANT] Run only the registration command. Do not run the Docker start command because the
> `make test` command completes this step.

Create a different registration file on each device testing the policy.

The registration file is ignored for storage in remote repositories and is listed in the
`.gitignore` file located at the root of the project directory.

## Configure Your Custom Policy

Each test function requires the configuration of the API and policies for that request.

To configure the custom policy:

1.  Print the policy ID by running the `show-policy-ref-name`:

    ```ssh
    make show-policy-ref-name
    ```

2.  Copy the result.

3.  Override `{{project-name}}` in `common/mod.rs` with your policy ID, for example:

    ```rust
    pub const POLICY_NAME: &str = "<custom-policy-id>";
    ```

4.  Configure the policy’s configuration parameters.

    To find your configuration parameters, see the `gcl.yaml` file.

## Create a Test Function

Add a new test module by placing a new test function in an existing test module, for example
`/tests/requests.rs`.

PDK test functions are `async` functions decorated with the `#[pdk_test]` attribute:

```rust
use pdk_test::pdk_test;

#[pdk_test]
async fn say_hello() {

}
```

> [!NOTE] Because the body is empty, this test function never fails. Run `make test` to ensure
> everything is working fine.

## Run an Integration Test

To run the integrations tests modules in the `/tests` folder, execute the `make test` command:

```ssh
make test
```

Run this test as you add additional features to your integration test to ensure proper
configuration.

> [!NOTE] The `make test` command compiles the policy before running the integration tests.

## Configure Services for Integration Testing

PDK integration tests contain a composite of services running inside a Docker network. You can
register service instances in a `TestComposite` object. Every service has a configuration, hostname,
and instance.

The `pdk-test` crate contains all the functionality for writing integration tests for custom
policies in PDK and is included in the `[dev-dependencies]` section of the `Cargo.toml` file.

### Supported Services

PDK’s test library supports the following services:

<table><colgroup><col> <col></colgroup><thead><tr><th>Service</th><th>Description</th></tr></thead><tbody><tr><td><p><code>Flex</code></p></td><td><p>Omni Gateway service instance.</p></td></tr><tr><td><p><code>httpmock</code></p></td><td><p>Service binding to the Rust’s <a href="https://crates.io/crates/httpmock">httpmock</a> server.</p></td></tr></tbody></table>

The supported services cover the majority of use cases for integration testing. It is not possible
to define different types of service.

### Configure an httpmock Service

`httpmock` is a mock server that enables you to write request mocks in Rust. Configure an
[httpmock](https://crates.io/crates/httpmock) service by using an `HttpMockConfig` value. It is a
best practice to return a `Result` from the test function. The
[anyhow](https://crates.io/crates/anyhow) Rust crate enables you to return different default error
types.

To configure an `httpmock` service, see the following code example:

```rust
use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::HttpMockConfig;

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {

    // Configure HttpMock service
    let backend_config = HttpMockConfig::builder()
        .hostname("backend")
        .port(80) // Port where the service will accept requests
        .build();

    // Register HTTPBin service and start the docker network
    let composite = TestComposite::builder()
        .with_service(backend_config)
        .build()
        .await?;

    Ok(())
}
```

> [!NOTE] If the Docker engine is properly configured, this test passes. Run `make test` to ensure
> proper configuration.

### Configure an Alternative httpmock Docker Image

To use an `httpmock` Docker image from another source, instead of the provided default, the
configuration builder provides an `image_name()` method to define it explicitly.

To configure an alternative `httpmock` Docker image, update your `HttpMockConfig::builder()`
definition as in this example:

```rust
// Configure HttpMock service
let backend_config = HttpMockConfig::builder()
    .image_name("myrepo/httpmock") // Custom image name setting
    .hostname("backend")
    .port(80) // Port where the service will accept requests
    .build();
```

### Get a Service’s Handle

Every configured service instance in the composite has a handle for custom interaction. The handle
returns the sockets where the service endpoints are available.

To find a service’s handle, see the following code example:

```rust
use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::{HttpMock, HttpMockConfig};

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {

    // Configure HTTPMock service
    let backend_config = HttpMockConfig::builder()
        .hostname("backend")
        .build();

    // Register HTTPMock service and start the docker network
    let composite = TestComposite::builder()
        .with_service(backend_config)
        .port(80) // Port where the service accepts requests
        .build()
        .await?;

    // Get the httpmock handle
    let httpmock: HttpMock = composite.service()?;

    Ok(())
}
```

> [!NOTE] If the `httpmock` instance is properly configured, this test passes. Run `make test` to
> ensure proper configuration.

### HTTP Endpoint Requests

To mock endpoints, use the
[httpmock When/Then API](https://docs.rs/httpmock/latest/httpmock/#whenthen-api).

After mocking the endpoint, use an HTTP client to make endpoint requests.

The [reqwest](https://crates.io/crates/reqwest/) crate offers the most comprehensive and flexible
Rust HTTP client.

To mock endpoints and make requests, see the following code example:

```rust
use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::{HttpMock, HttpMockConfig};

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {

    // Configure HttpMock service
    let backend_config = HttpMockConfig::builder()
        .hostname("backend")
        .port(80) // Port where the service will accept requests
        .build();

    // Register HTTPBin service and start the docker network
    let composite = TestComposite::builder()
        .with_service(backend_config)
        .build()
        .await?;

    // Get the httpmock handle
    let httpmock: HttpMock = composite.service()?;

    // Connect the mock server
    let mock_server = httpmock::MockServer::connect_async(httpmock.socket()).await;

    // Configure the endpoint mocks
    mock_server.mock_async(|when, then| {
        when.path_contains("/hello");
        then.status(202).body("World!");
    }).await;

    let base_url = mock_server.base_url();

    // Hit the endpoint
    let response = reqwest::get(format!("{base_url}/hello")).await?;

    // Assert on response
    assert_eq!(response.status(), 202);
    assert_eq!(response.text().await?, "World!");

    Ok(())
}
```

> [!NOTE] Run `make test` to ensure proper configuration.

### Configure an Omni Service Instance

Configure a `Flex` service by registering an instance of the `FlexConfig` struct in `TestComposite`.
To complete the `FlexConfig` instance, you must also define a `ApiConfig` and `PolicyConfig`
instance.

The `FlexConfig` struct has the following properties:

<table><colgroup><col> <col></colgroup><thead><tr><th>Property</th><th>Content</th></tr></thead><tbody><tr><td><p><code>version</code></p></td><td><p>Omni Gateway version to test.</p></td></tr><tr><td><p><code>image_name</code></p></td><td><p>Omni Gateway Docker image name, by default, "mulesoft/flex-gateway".</p></td></tr><tr><td><p><code>hostname</code></p></td><td><p>Hostname of the <code>Flex</code> service, by default, "local-flex".</p></td></tr><tr><td><p><code>config_mounts</code></p></td><td><p>A map of local directories where the <code>Flex</code> service finds configuration files. The <code>POLICY_DIR</code> and <code>COMMON_CONFIG_DIR</code> constants are defined in the <code>common/mod.rs</code> module by default. If your test requires further configuration, add a directory with the specific configuration for the test functions.</p></td></tr><tr><td><p><code>with_api</code></p></td><td><p>The <code>ApiConfiguration</code> of the API deployed to the <code>Flex</code> service instance. Call multiple times to configure multiple APIs.</p></td></tr><tr><td><p><code>ports</code></p></td><td><p>List of ports where the <code>Flex</code> service listens for requests. If any of your configuration directories included in <code>config_mount</code> directories expose an API, the port must match the ports configured in the <code>spec.address</code> property.</p></td></tr></tbody></table>

The `ApiConfig` has the following properties:

<table><colgroup><col> <col></colgroup><thead><tr><th>Property</th><th>Content</th></tr></thead><tbody><tr><td><p><code>name</code></p></td><td><p>Name of the API.</p></td></tr><tr><td><p><code>upstream</code></p></td><td><p>The reference to the upstream service.</p></td></tr><tr><td><p><code>path</code></p></td><td><p>Path to which the upstream requests are sent.</p></td></tr><tr><td><p><code>port</code></p></td><td><p>Ports where the API listens for requests.</p></td></tr><tr><td><p><code>policies</code></p></td><td><p>A List of <code>PolicyConfig</code> instances applied to the API.</p></td></tr></tbody></table>

The `PolicyConfig` has the following properties:

<table><colgroup><col> <col></colgroup><thead><tr><th>Property</th><th>Content</th></tr></thead><tbody><tr><td><p><code>name</code></p></td><td><p>Policy to apply.</p></td></tr><tr><td><p><code>configuration</code></p></td><td><p>Parameters for the policy.</p></td></tr></tbody></table>

The following code example defines a complete `Flex` service:

```rust
mod common;
use common::*;

use pdk_test::{pdk_test, TestComposite};
use pdk_test::port::Port;
use pdk_test::services::flex::FlexConfig;

// Port where Flex listens for requests
const FLEX_PORT: Port = 8081;

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {

    // Configure an upstream service for the API
    let httpmock_config = HttpMockConfig::builder()
        .port(80)
        .version("latest")
        .hostname("backend")
        .build();

    // Configure a policy for the API
    let policy_config = PolicyConfig::builder()
        .name(POLICY_NAME)
        .configuration(serde_json::json!({"source": "http://backend/blocked", "frequency": 60}))
        .build();

    // Configure the API to deploy to the Flex
    let api_config = ApiConfig::builder()
        .name("ingress-http")
        .upstream(&httpmock_config)
        .path("/anything/echo/")
        .port(FLEX_PORT)
        .policies([policy_config])
        .build();

    // Configure the Flex service
    let flex_config = FlexConfig::builder()
        .version("1.6.1")
        .hostname("local-flex")
        .config_mounts([
            (POLICY_DIR, "policy"),
            (COMMON_CONFIG_DIR, "common")
        ])
        .with_api(api_config)
        .build();

    let composite = TestComposite::builder()
        .with_service(flex_config)
        .with_service(httpmock_config)
        .build()
        .await?;
}
```

### Configure an Alternative Omni Docker Image

To use a `flex` Docker image from another source, instead of the provided default, the configuration
builder provides an `image_name()` method to define it explicitly.

To configure an alternative Omni Docker image, update your `FlexConfig::builder()` definition as in
this example:

```rust
// Configure the Flex service
let flex_config = FlexConfig::builder()
    .image_name("myrepo/flex-gateway") // Custom image name setting
    .version("1.6.1")
    .hostname("local-flex")
    .config_mounts([
        (POLICY_DIR, "policy"),
        (COMMON_CONFIG_DIR, "common")
    ])
    .with_api(api_config)
    .build();
```

> [!NOTE] Run `make test` to ensure proper configuration.

### Omni Service Endpoint Requests

`Flex` service exposes endpoints with external base URLs dependent on the port. To access those
URLs, the Omni service instance handle provides the `external_url()` method indexed by port, for
example:

```rust
use pdk_test::{pdk_test, TestComposite, port::Port};
use pdk_test::services::flex::Flex;

const FLEX_PORT: Port = 8081;

async fn build_composite() -> anyhow::Result<TestComposite> {
    todo!("Configuration stuff comes here")
}

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {
    // Invoke a helper composite builder function for simplicity
    let composite = build_composite().await?;

    // Get the Flex service handle
    let flex: Flex = composite.service()?;

    // Get the URL for our configured port
    let flex_url = flex.external_url(FLEX_PORT).unwrap();

    let response = reqwest::get(format!("{flex_url}/hello")).await?;

    // Make assertions

    Ok(())
}
```

### Combine Multiple Services

Usually a PDK integration test includes a `Flex` service and at least one backend service.

The following code is the default test generated when the custom policy is created:

```rust
mod common;

use httpmock::MockServer;
use pdk_test::{pdk_test, TestComposite};
use pdk_test::port::Port;
use pdk_test::services::flex::{FlexConfig, Flex};
use pdk_test::services::httpmock::{HttpMockConfig, HttpMock};

use common::*;

// Directory with the configurations for the `hello` test.
const HELLO_CONFIG_DIR: &str =  concat!(env!("CARGO_MANIFEST_DIR"), "/tests/requests/hello");

// Flex port for the internal test network
const FLEX_PORT: Port = 8081;

// This integration test shows how to build a test to compose a local-flex instance
// with a MockServer backend
#[pdk_test]
async fn hello() -> anyhow::Result<()> {
    // Configure an HttpMock service
    let httpmock_config = HttpMockConfig::builder()
        .port(80)
        .version("latest")
        .hostname("backend")
        .build();

    // Configure a policy for the API
    let policy_config = PolicyConfig::builder()
        .name(POLICY_NAME)
        .configuration(serde_json::json!({"source": "http://backend/blocked", "frequency": 60}))
        .build();

    // Configure the API to deploy to the Flex
    let api_config = ApiConfig::builder()
        .name("ingress-http")
        .upstream(&httpmock_config)
        .path("/anything/echo/")
        .port(FLEX_PORT)
        .policies([policy_config])
        .build();

    // Configure the Flex service
    let flex_config = FlexConfig::builder()
        .version("1.6.1")
        .hostname("local-flex")
        .config_mounts([
            (POLICY_DIR, "policy"),
            (COMMON_CONFIG_DIR, "common")
        ])
        .with_api(api_config)
        .build();

    // Compose the services
    let composite = TestComposite::builder()
        .with_service(flex_config)
        .with_service(httpmock_config)
        .build()
        .await?;

    // Get a handle to the Flex service
    let flex: Flex = composite.service()?;

    // Get an external URL to point the Flex service
    let flex_url = flex.external_url(FLEX_PORT).unwrap();

    // Get a handle to the HttpMock service
    let httpmock: HttpMock = composite.service()?;

    // Create a MockServer
    let mock_server = MockServer::connect_async(httpmock.socket()).await;

    // Mock a /hello request
    mock_server.mock_async(|when, then| {
        when.path_contains("/hello");
        then.status(202).body("World!");
    }).await;

    // Perform an actual request
    let response = reqwest::get(format!("{flex_url}/hello")).await?;

    // Assert on the response
    assert_eq!(response.status(), 202);

    Ok(())
}
```

## Review Service Logs

After an integration test fails, you can view the service logs to find errors. To improve the
debugging experience, there is a dedicated service log in each test folder at
`<root-directory>/target/pdk-test/<module-name>/<test>/<service>.log`.

For the example, the `say_hello` test has the following service log:

```none
<root-directory>
├── Cargo.lock
├── Cargo.toml
├── playground
├── src
├── tests
└── target
    └── pdk-test
        └── requests
            └── say_hello
                └── <service>.log
```

## See Also

- [Debugging Custom Policies with the PDK Debugging Playground](https://docs.mulesoft.com/pdk/1.8/policies-pdk-debug-local)

- [PDK Troubleshooting](https://docs.mulesoft.com/pdk/1.8/policies-pdk-troubleshooting)
