Skip to main content

Testing APIs

It's important users don't hit errors when running your APIs. By writing tests, you can confirm business logic is working as expected, APIs are playing nicely with integrations, and your users have a bug free experience.

With the Superblocks SDK and your favorite JavaScript or Python test framework, you can:

  • Write end-to-end API tests to verify API outputs when your app is in various states
  • Validate your API's execution at every step by checking step outputs
  • Test compatibility between your API and other systems, like 3rd-party APIs
  • Run tests locally as you develop to get immediate feedback on changes
  • Run tests in your CI/CD pipeline to make sure changes are ready to deploy

Set up

To create tests, start by writing and running tests locally. First, navigate to your app's git repo used for Source Control or create a new local directory for tests.

mkdir superblocks
cd superblocks
mkdir tests

In these examples, we'll show how to write tests with JEST and PyTest. Use the following to configure your project:

Add the following to the package.json at the root of your project directory:

package.json
{
"name": "superblocks",
"scripts": {
"test": "jest --setupFiles=dotenv/config"
},
"dependencies": {
"@superblocksteam/agent-sdk": "*",
"dotenv": "^16.4.5"
},
"devDependencies": {
"jest": "^29.7.0"
}
}

From your terminal, run:

npm install

To execute your APIs with the SDK, you'll need an execute token. Find your token in your Personal Settings. Copy it and add it to a .env file at the root of your directory.

.env
SUPERBLOCKS_EXECUTION_TOKEN=<YOUR_EXECUTION_TOKEN>
caution

If you're adding tests to a git repo, make sure to include .env in your .gitignore file.

Writing your first test

Create a test file

You'll end up with a project that looks like:

.
├── .env
├── .gitignore
├── package.json
└── tests
└── sample.test.js
info

As your test suite grows you'll likely want to group the tests for an App, App Page, or related Workflows together. Learn more about organizating test files.

Add a test case that calls your API

The basic unit of tests in Superblocks is an API. APIs can be Backend APIs, Workflows, or Scheduled Jobs. While you can create tests for multiple APIs, we'll start by testing the functionality in a single API.

The following test checks that a simple API with no inputs returns data.

tests/sample.test.js
const { Api, Client } = require('@superblocksteam/agent-sdk');

client = new Client({
config: {
token: process.env.SUPERBLOCKS_EXECUTION_TOKEN
}
}) ;

describe('My APIs Test Suite', () => {
// Initalize the API client for the API we will test
const api = new Api('<YOUR_API_ID>');

test('Test API returns', async () => {
let req = await api.run({}, client);
expect(req.getResult());
});
});
caution

Your client config will need to be slightly different if your organization uses Superblocks On-Premise of Superblocks EU. See the docs on On-Premise Agent & EU setup

Finding API IDs

Find the ID of Backend APIs by clicking Copy ID in the options menu next to the API's name.

Copy Backend API ID

For Workflows and Scheduled Jobs, copy the ID from the URL in your browser.

Run your test

Finally, run npm test and Jest will print the results of your test.

$ npm test

PASS tests/sample.test.js
My API Test Suite
✓ Test API returns (428 ms)

Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.813 s
Ran all test suites.

Mocking inputs

Your API has inputs if it references parts of your app's state. For example, if there are bindings that reference Component Properties, Frontend Variables, the Global object, etc.

You can determine if your API requires input, by looking at the Inputs in your API's configuration.

Backend API Inputs

To mock your API's inputs, follow the format shown in the Inputs section in your API call:

tests/sample.test.js
# ...

describe('My APIs Test Suite', () => {
# ...

test('Test API returns', async () => {
let req = await api.run({
inputs: {
user_filter: {
selectedOptionValue: "70bb2118070943b9909f2e265813408b"
}
}
}, client);
expect(req.getResult());
});
});
info

Testing a Workflow? Just add the Workflow's params and body to the inputs of the API.

Mocking steps

If you don't want Superblocks to make calls to external services when testing, you can use mock data instead. In this situation, you must use mock data to mock all of the steps that involve interaction with third-party integrations.

For example, lets say we have an API that counts the number of Zendesk tickets in each status. We don't want to make requests to Zendesk since it may lead us to hitting rate limits. Instead, we'll mock our API's get_tickets step to return example tickets.

tests/sample.test.js
const { Api, Client, on } = require('@superblocksteam/agent-sdk');

# ...

describe('My APIs Test Suite', () => {
# ...

test('Test API with mock', async () => {
let mock_tickets = on({ stepName: "get_tickets" }).return([{
"results": [
{ "id": 1, "status": "new"},
{ "id": 2, "status": "new" },
{ "id": 3, "status": "solved" }
]
}]);

let req = await api.run({ mocks: [mock_tickets] }, client);
let results = req.getResult();

// We should see 2 new and 1 solved ticket
expect(results.new).toBe(2);
expect(results.solved).toBe(1);
});
});
info

Learn more about types of mocks you can create in Advanced testing topics.

Assert on step output

So far, we've written tests that check the output of the entire API. You can also use the SDK to test individual steps of your API.

Say we have an API that assigns Zendesk tickets to agents. Users can choose to "auto" assign the ticket. If this option is selected, the API checks the number of open tickets assigned to each agent and returns the agent with the lowest ticket count in the assign_to step.

In our test we will:

  1. Mock the get_ticket_cnts step that returns the number of tickets each agent is assigned in Zendesk
  2. Get the output of the assign_to step to test that it selected the user with the lowest ticket count
tests/sample.test.ts
const { Api, Client, on } = require('@superblocksteam/agent-sdk');

# ...

describe('Test Zendesk ticket assignment', () => {
# ...

test('should assign to agent with lowest ticket count', async () => {
let input = {
tickets: {
selectedRow: {
id: "ticket_id"
}
},
assignTo: "Auto"
};
let mockCnts = on({ stepName: "get_tickets_cnts" }).return([{
results: [
{ agentId: "1", displayName: "Alex Thompson", cnt: 23 },
{ agentId: "2", displayName: "Jamie Rivera", cnt: 13 }.
{ agentId: "3", displayName: "Morgan Patel", cnt: 19 },
{ agentId: "4", displayName: "Taylor Kim", cnt: 21 }
]
}]);

let req = await api.run({ inputs: inputs, mocks: [mockCnts] }, client);

// Only get the results of the assign_to step
let results = req.getBlockResult("assign_to");

// Should say to assign to Jamie Rivera since they have 13 tickets
expect(results.agentId).toBe("2");
});
});

Advanced testing topics

On-Premise Agent & EU setup

If your organization uses the On-Premise Agent or Superblocks Cloud in the EU, you'll need to update your test environment set up so the SDK sends requests to the right agent to execute your code. To update your set up, add the following:

Update your .env file by adding the following:

SUPERBLOCKS_EXECUTION_TOKEN=<YOUR_EXECUTION_TOKEN>
SUPERBLOCKS_DATA_DOMAIN=<YOUR_AGENT_HOST_URL | agent.eu.superblocks.com>
Choosing an Agent

Your organization may have agents deployed in multiple environments. If you do, you'll need to choose which agent the SDK sents requests to. To do this, select an agent that's tagged with the Profile you're trying to test against.


For example, if you're testing against the development profile, choose an agent that has the profile:development tag.


You can find agents and how they're tagged on the On-Premise Agents page.

Update the client configuration in your test file to include the following:

const { Api, Client } = require('@superblocksteam/agent-sdk');

client = new Client({
config: {
token: process.env.SUPERBLOCKS_EXECUTION_TOKEN,
endpoint: process.env.SUPERBLOCKS_DATA_DOMAIN
}
});

describe('My APIs Test Suite', () => {
# ...
});

Testing code on a branch

By default, the SDK runs API code on the main branch of your App. If you use Source Control, you can test API code that's on branches by updating the SDK client configuration. The following recipes show how to configure this so that the branch is based on the branch you have checked out using git.

Update your package.json as follows:

package.json
{
"scripts": {
"test": "BRANCH=$(git rev-parse --abbrev-ref HEAD) jest --setupFiles=dotenv/config"
},
"devDependencies": {
"jest": "^29.7.0"
},
"dependencies": {
"@superblocksteam/agent-sdk": "^0.0.23",
"dotenv": "^16.4.5"
}
}

When initializing your SDK Client, include:

const client = new Client({
config: {
token: process.env.SUPERBLOCKS_EXECUTION_TOKEN
},
defaults: {
branch: process.env.BRANCH || null,
},
});

Organizing test files

To easily run your tests in a CI/CD pipeline, we recommend keeping all of your app's tests in one tests directory.

If you're writing tests in conjunction with Source Control, your repo will end up looking as follows:

.
├── .env
├── .gitignore
├── .superblocks
├── package.json
├── apps
│ └── my_app
│ ├── .superblocks
│ │   └── superblocks.json
│ ├── application.yaml
│ └── pages
│    └── home
│    ├── apis
│    │   └── api1.yaml
│    └── page.yaml
└── tests
└── my_app
└── api1.test.py

Advanced mocking

Mocks let you isolate specific parts of your code to test it without accessing external dependencies. This helps improve the performance of your tests, reduces flakiness related to connectivity issues, and helps you avoid unnecessarily polluting services with data each time your test runs.

What types of mocking are available

The SDK lets you mock step data output in various ways. You can mock step data based on the step's name, integration type, and based on step configurations.

Mock output of a step

When you mock based on the step name, Superblocks won't hit external data sources or send HTTP requests when that step of the API runs. Instead, it will return the mock data you've provided as the step's output.

To mock a step based on its name:

import { on, Mock, Params } from "@superblocksteam/agent-sdk";

const mock = on({ stepName: 'MyStep' }).return([{ order: 123 }]);
Mock output of an integration

If you'd like to create a mock that can be re-used across steps or even API tests, you can create mocks based on the integration type, for example Postgres.

import { on, Mock, Params } from "@superblocksteam/agent-sdk";

const mock = on({ integrationType: 'postgres' }).return([{ order: 123 }]);
Mock output based on step config

For the most flexible mocks, you can mock based on a step's configuration. For example, lets say we wanted to return mock data anytime we make requests to Zendesk's tickets API. You can achieve this using the following mock:

import { on, Mock, Params } from "@superblocksteam/agent-sdk";

const mock = on({
integrationType: 'zendesk',
configuration: {
httpMethod: 'GET',
urlPath: '/api/v2/tickets'
}
}).return({
"results": [
{ "id": 1, "status": "new"},
{ "id": 2, "status": "new" },
{ "id": 3, "status": "solved" }
]
});

Mocks also support callback functions to dynamically return data based on the step's configuration. For example, the following mock returns on solved tickets when the step includes the query parameter query=status:new

import { on, Mock, Params } from "@superblocksteam/agent-sdk";

const mock = on({
integrationType: "zendesk",
configuration: {
httpMethod: "GET",
urlPath: "/api/v2/tickets"
}
}).return(({ configuration }) => {
let query = configuration.params.filter(item => item.key === "query");
if (query && query.value === "status:new") {
return {
"results": [
{ "id": 1, "status": "new"},
{ "id": 2, "status": "new" },
]
}
} else {
return {
"results": [
{ "id": 1, "status": "new"},
{ "id": 2, "status": "new" },
{ "id": 3, "status": "solved" }
]
}
});

Steps that can't be mocked

Certain block types in Superblocks don't support mocking, including:

Testing code on a branch

By default, the SDK runs API code on the main branch of your App. If you use Source Control, you can test API code that's on branches by updating the SDK client configuration. The following recipes show how to configure this so that the branch is based on the branch you have checked out using git.

Update your package.json as follows:

package.json
{
"scripts": {
"test": "BRANCH=$(git rev-parse --abbrev-ref HEAD) jest --setupFiles=dotenv/config"
},
"devDependencies": {
"jest": "^29.7.0"
},
"dependencies": {
"@superblocksteam/agent-sdk": "^0.0.23",
"dotenv": "^16.4.5"
}
}

When initializing your SDK Client, include:

const client = new Client({
config: {
token: process.env.SUPERBLOCKS_EXECUTION_TOKEN
},
defaults: {
branch: process.env.BRANCH || null,
},
});

Run tests in CI/CD pipeline

Automatically executing API tests during development ensures that each code change you make is production-ready. Since your tests are written in code, you can easily integrate your tests into your CI/CD pipeline. This allows your tests to run automatically with every code push to your repository.

To configure your CI/CD to run tests automatically, follow the instructions below based on your git provider.

  1. Open your GitHub repo. This should be the repo you use for Source Control for your app.
  2. Navigate to the repository settings
  3. Go to Security section, click Secrets and variablesActions
  4. Add the following as a secret to your repository:
NameSUPERBLOCKS_EXECUTION_TOKEN
Secret

Execution token for your organization. You can find this token at https://app.superblocks.com/personal-settings#apiKey

  1. Save the secret and go back to your repository's main page
  2. Open the GitHub workflow file for your Source Control CI
  3. Add the run-tests job to the workflow that corresponds with your testing language
.github/sync_to_superblocks.yaml
name: Sync changes to Superblocks
on: [push]

jobs:
superblocks-sync:
runs-on: ubuntu-latest
name: Sync to Superblocks
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Push
uses: superblocksteam/import-action@v1
id: push
with:
token: ${{ secrets.SUPERBLOCKS_TOKEN }}
domain: ${{ secrets.SUPERBLOCKS_DOMAIN }}

- name: Pull
uses: superblocksteam/export-action@v1
id: pull
with:
token: ${{ secrets.SUPERBLOCKS_TOKEN }}
domain: ${{ secrets.SUPERBLOCKS_DOMAIN }}

run-tests:
runs-on: ubuntu-latest
needs: superblocks-sync
steps:
- name: checkout
uses: actions/checkout@v4

- name: setup node
uses: actions/setup-node@v4

# Run JavaScript tests
- name: run js tests
run: |
npm install
npm test
env:
SUPERBLOCKS_EXECUTIONS_TOKEN: ${{ secrets.SUPERBLOCSK_EXECUTION_TOKEN }}
SUPERBLOCKS_DOMAIN: ${{ secrets.SUPERBLOCKS_DOMAIN }}
SUPERBLOCKS_VIEW_MODE: preview
BRANCH: ${{ github.ref_name }}

# Run Python tests
- name: run python tests
run: |
pip3 install -r requirements.txt
pytest
env:
SUPERBLOCKS_EXECUTIONS_TOKEN: ${{ secrets.SUPERBLOCSK_EXECUTION_TOKEN }}
SUPERBLOCKS_DOMAIN: ${{ secrets.SUPERBLOCKS_DOMAIN }}
SUPERBLOCKS_VIEW_MODE: preview
BRANCH: ${{ github.ref_name }}