# Test applications locally Run service, event, and job applications on your machine to confirm they behave as expected before you publish a Connector. Before you publish a Connector, test each of its [Connect applications](/connect/overview.md#connect-applications) locally to confirm they behave as expected. Local testing lets you exercise your business logic, catch errors early, and iterate without deploying. This guide covers the three application types that expose an endpoint: `service`, `event`, and `job`. For guidance on writing automated tests, see [Develop a Connect application](/connect/development.md#test-your-connect-application). ## How local testing works Service, event, and job applications run as HTTP servers that listen on port `8080`. Each application exposes a single endpoint whose path matches the `endpoint` value in [`connect.yaml`](/connect/development.md#configure-connectyaml), such as `/service`, `/event`, or `/job`. All three endpoints accept `POST` requests. In production, Connect triggers each application type through different infrastructure. Locally, that infrastructure is not present, so you simulate a trigger by sending a `POST` request with a representative payload to the application's endpoint. | Application type | Trigger in production | Local test | | --- | --- | --- | | Service | An [API Extension](/api/projects/api-extensions.md) or webhook | POST an API Extension payload | | Event | A [Subscription](/api/projects/subscriptions.md) message sent through a broker | POST a message payload | | Job | A scheduler that runs on a cron expression | POST to the endpoint with no schedule control | ## Run your application locally The starter templates configure each application as an HTTP server that listens on port `8080`. Build and run the application you want to test before you send requests to it. Open the application's folder (`service`, `event`, or `job`) and use the following commands: - `yarn` installs dependencies. - `yarn build` compiles the application, and `yarn start` runs the compiled output. Alternatively, `yarn start:dev` runs the application in watch mode and rebuilds automatically when you change the source. - `yarn test` runs the automated tests included with the templates. These tests send requests to the application's routes, so they are a fast way to verify behavior without starting the server. For the full list of commands, see [Develop a Connect application](/connect/development.md#manage-the-application-with-yarn-commands). ## Test a service application Service applications respond to [API Extensions](/api/projects/api-extensions.md). When triggered, the application receives an action and the affected resource. It applies its logic and returns a list of update actions in the [API Extension response](/api/projects/api-extensions.md#response) format. Consider a service application that applies an automatic 10% discount to a Cart. To test it, send a `POST` request that contains a sample Cart in the body, then confirm the application returns the expected update actions. The following example shows a sample request body and the update actions that the application returns. ```json title="Sample POST body" { "action": "Create", "resource": { "typeId": "cart", "id": "bcaeb291-5151-434c-80a9-c1ce7c88e6e0", "obj": { "type": "Cart", "id": "bcaeb291-5151-434c-80a9-c1ce7c88e6e0", "version": 1, "lineItems": [ { "id": "aab03288-fd14-4bb1-9a23-e506774ac040", "productId": "56fc033e-25de-484b-bf31-fc0b97143aa9", "name": { "en": "Some Product" }, "variant": { "id": 1, "sku": "SKU-fake", "prices": [ { "id": "a09471bc-d9d6-4147-a390-7ac558f93c3c", "value": { "type": "centPrecision", "currencyCode": "EUR", "centAmount": 4000, "fractionDigits": 2 } } ] }, "quantity": 1, "totalPrice": { "type": "centPrecision", "currencyCode": "EUR", "centAmount": 4000, "fractionDigits": 2 } } ], "cartState": "Active", "totalPrice": { "type": "centPrecision", "currencyCode": "EUR", "centAmount": 4000, "fractionDigits": 2 }, "taxMode": "Platform", "origin": "Customer", "totalLineItemQuantity": 1 } } } ``` ```json title="Expected response" { "actions": [ { "action": "setDirectDiscounts", "discounts": [ { "value": { "type": "relative", "permyriad": 1000 }, "target": { "type": "lineItems", "predicate": "1=1" } } ] } ] } ``` Save the sample body to a file, such as `cart.json`, and send it to the application's endpoint: ```shell title="Call the service application" curl -X POST http://localhost:8080/service -H 'Content-Type: application/json' -d @cart.json ``` The `action` field determines whether the payload represents a created or updated resource. Set it to `Create` or `Update` to match the scenario you want to test. Check the returned update actions to confirm they match your application's logic. A service application must reply with a `200` status code. ### Test with a live API Extension Instead of sending sample data, you can create a real API Extension that calls your local application. This tests the full round trip from commercetools to your application. #### Requirements - Your application runs locally and exposes an HTTP server on port `8080`. - A tool that exposes your local server to the internet, such as [ngrok](https://ngrok.com/). #### Expose your application An API Extension calls your application over the internet, so your local server needs a public URL. The following example uses ngrok to expose port `8080`: ```shell title="Expose port 8080" ngrok http 8080 # Example public URL: https://public-sample.ngrok-free.app ``` #### Create the API Extension Using the public URL, create an API Extension that triggers when a Cart is created or updated: ```shell title="Create an API Extension" curl --location 'https://api.{region}.commercetools.com/{projectKey}/extensions' --header 'Content-Type: application/json' --header 'Authorization: Bearer ' --data '{ "destination": { "type": "HTTP", "url": "https://public-sample.ngrok-free.app" }, "triggers": [ { "resourceTypeId": "cart", "actions": ["Create", "Update"] } ], "key": "my-extension" }' ``` #### Trigger the API Extension Create or update a Cart to trigger the API Extension: ```shell title="Create a Cart" curl --location 'https://api.{region}.commercetools.com/{projectKey}/carts' --header 'Content-Type: application/json' --header 'Authorization: Bearer ' --data '{ "currency": "EUR", "lineItems": [ { "productId": "{productId}", "variantId": 1 } ] }' ``` Review your application's logs and the affected resource to confirm the application ran as expected. ## Test an event application Event applications process messages delivered by a [Subscription](/api/projects/subscriptions.md). The application receives a message envelope, acts on the message, and returns a success status code. The message arrives in the following envelope. The `message.data` field is base64-encoded, and its decoded value is a commercetools message. ```json title="Message envelope" { "message": { "attributes": { "key": "value" }, "data": "", "messageId": "2070443601311540", "publishTime": "2021-02-26T19:13:55.749Z" }, "subscription": "projects/myproject/subscriptions/mysubscription" } ``` The decoded message uses either the [PlatformFormat](/urn?urn=ctp%3Aapi%3Atype%3APlatformFormat) or the [CloudEventsFormat](/urn?urn=ctp%3Aapi%3Atype%3ACloudEventsFormat), depending on the format set when you [create the Subscription](/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Fsubscriptions%3APOST). The following examples show a decoded message for a newly created Order in each format. ```json title="PlatformFormat message" { "notificationType": "Message", "projectKey": "{projectKey}", "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "version": 1, "sequenceNumber": 1, "resource": { "typeId": "order", "id": "12345678-90ab-cdef-1234-567890abcdef" }, "resourceVersion": 1, "type": "OrderCreated", "order": { "id": "12345678-90ab-cdef-1234-567890abcdef", "version": 1, "orderNumber": "ORD-10001", "orderState": "Open", "totalPrice": { "currencyCode": "USD", "centAmount": 15000 } }, "createdAt": "2025-07-09T10:15:30.000Z", "lastModifiedAt": "2025-07-09T10:15:30.000Z" } ``` ```json title="CloudEventsFormat message" { "specversion": "1.0", "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "type": "com.commercetools.order.message.OrderCreated", "source": "/{projectKey}/orders/12345678-90ab-cdef-1234-567890abcdef", "subject": "OrderCreated", "time": "2025-07-09T10:15:30.000Z", "data": { "notificationType": "Message", "projectKey": "{projectKey}", "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", "version": 1, "sequenceNumber": 1, "resource": { "typeId": "order", "id": "12345678-90ab-cdef-1234-567890abcdef" }, "resourceVersion": 1, "type": "OrderCreated" } } ``` To call the application, base64-encode a message and send it in the `message.data` field of the envelope: ```shell title="Call the event application" curl -X POST http://localhost:8080/event -H 'Content-Type: application/json' -d '{ "message": { "data": "" } }' ``` To generate the encoded value, base64-encode the message JSON. For example, run `base64 < message.json` and copy the result into `message.data`. Check your application's logs and any third-party integrations to confirm the message was processed. An event application must reply with a success status code, such as `200` or `204`. ## Test a job application Job applications run on a schedule in production. A scheduler sends a `POST` request to the application's endpoint based on the cron expression set in [`properties.schedule`](/connect/development.md#configure-connectyaml). You can override this schedule for a specific deployment using the `schedule` field of [DeploymentConfigurationApplication](/connect/deployments.md#deploymentconfigurationapplication). Locally, no scheduler is available, so you trigger the application manually by sending a `POST` request to its endpoint. The scheduling behavior cannot be reproduced or controlled locally. ```shell title="Call the job application" curl -X POST http://localhost:8080/job ``` A job application does not require a request body. The scheduler triggers the task, so the application runs independently of the request content. Check your application's logs and any affected resources to confirm the task ran as expected. A job application must reply with a `200` status code. ## Related pages - [Area overview page with navigation](/connect.md) - [Previous page: Convert an existing integration](/connect/convert-existing-integration.md) - [Next page: Use logs to debug a Deployment](/connect/how-to-debug-a-deployment.md)