Testing custom functions

In order to develop on the next-generation platform, your workspace must be on a paid plan.

During development, you may want to test your custom functions before deploying them to production. You can do this by creating a unit test for each custom function you want to validate. Since we're developing in the Deno environment, we'll be working with the Deno.test API.

Let's walk through a couple of examples from our sample apps.

Testing a custom function

Using the SlackFunctionTester, we can specify the inputs to a function and then verify the outputs that function provides in order to ensure it is working properly. In other words, the SlackFunctionTester allows us to create the context for our function so that we can pass in the necessary parameters in order to test that function. Let's get started!


The first thing we'll do is create a new test file named after our function.

For example, in the Hello World sample app, the file containing our function is called greeting_function.ts, and the file containing our test for the function is called greeting_function_test.ts.

We'll import our function into the test file as follows:

import GreetingFunction from "./greeting_function.ts";

Then, we'll import SlackFunctionTester into the test file:

import { SlackFunctionTester } from "deno-slack-sdk/mod.ts";

And, one more import β€” the specific Deno assertion method that we'll be using from the Deno.test API. In this case, we'll need the assertEquals method:

import { assertEquals } from "https://deno.land/std@0.153.0/testing/asserts.ts";

We can initialize an instance of the SlackFunctionTester we mentioned earlier to create a context for our function:

const { createContext } = SlackFunctionTester("greeting_function");

To summarize our structure, here is the original file containing our function:

// greeting_function.ts

import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";

export const GreetingFunctionDefinition = DefineFunction({
  callback_id: "greeting_function",
  title: "Generate a greeting",
  description: "Generate a greeting",
  source_file: "functions/greeting_function.ts",
  input_parameters: {
    properties: {
      recipient: {
        type: Schema.slack.types.user_id,
        description: "Greeting recipient",
      },
      message: {
        type: Schema.types.string,
        description: "Message to the recipient",
      },
    },
    required: ["message"],
  },
  output_parameters: {
    properties: {
      greeting: {
        type: Schema.types.string,
        description: "Greeting for the recipient",
      },
    },
    required: ["greeting"],
  },
});

export default SlackFunction(
  GreetingFunctionDefinition,
  ({ inputs }) => {
    const { recipient, message } = inputs;
    const salutations = ["Hello", "Hi", "Howdy", "Hola", "Salut"];
    const salutation =
      salutations[Math.floor(Math.random() * salutations.length)];
    const greeting =
      `${salutation}, <@${recipient}>! :wave: Someone sent the following greeting: \n\n>${message}`;
    return { outputs: { greeting } };
  },
);

And here we have our test file with the items we imported and our instance of the SlackFunctionTester:

// greeting_function_test.ts

import GreetingFunction from "./greeting_function.ts";
import { SlackFunctionTester } from "deno-slack-sdk/mod.ts";
import { assertEquals } from "https://deno.land/std@0.153.0/testing/asserts.ts";

const { createContext } = SlackFunctionTester("greeting_function");

Deno.test("Greeting function test", async () => {
  const inputs = { message: "Welcome to the team!" };
  const { outputs } = await GreetingFunction(createContext({ inputs }));
  assertEquals(
    outputs?.greeting.includes("Welcome to the team!"),
    true,
  );
});

Once we pass in the text we expect our function to output, we compare the two values, then check to see if the values are indeed a match.


Let's look at another example, this time from the GitHub Issue sample app.

Similarly to the Hello World example, we have a file containing our function called create_issue.ts, and a file containing our test for the function, which is called create_issue_test.ts. Let's look at the test file below:

// create_issue_test.ts

import * as mf from "https://deno.land/x/mock_fetch@0.3.0/mod.ts";
import { assertEquals } from "https://deno.land/std@0.153.0/testing/asserts.ts";
import { SlackFunctionTester } from "deno-slack-sdk/mod.ts";
import handler from "./create_issue.ts";

mf.install();

mf.mock("POST@/api/apps.auth.external.get", () => {
  return new Response(`{"ok": true, "external_token": "example-token"}`);
});

mf.mock("POST@/repos/slack-samples/deno-github-functions/issues", () => {
  return new Response(
    `{"number": 123, "html_url": "https://www.example.com/expected-html-url"}`,
    {
      status: 201,
    },
  );
});

const { createContext } = SlackFunctionTester("create_issue");
const env = { logLevel: "CRITICAL" };

Deno.test("Create a GitHub issue with given inputs", async () => {
  const inputs = {
    githubAccessTokenId: {},
    url: "https://github.com/slack-samples/deno-github-functions",
    githubIssue: {
      title: "The issue title",
    },
  };
  const { outputs } = await handler(createContext({ inputs, env }));
  assertEquals(outputs?.GitHubIssueNumber, 123);
  assertEquals(
    outputs?.GitHubIssueLink,
    "https://www.example.com/expected-html-url",
  );
});

This sample makes HTTP requests/API calls to both Slack and GitHub, and therefore requires special mocking in its test. In the test, we'll import a module called mock fetch. This module mocks Deno's fetch method, which is used to make HTTP requests. We will use mock-fetch to mock the responses of the Slack API.

We're also importing our original function in the form of a handler.

Our test will then assert whether the collection of mocked URL responses we create as inputs matches the outputs from our function.

✨ For more information about mocking responses, refer to mocking and mock_fetch.

Running a test

From the command line, run deno test and call the file that contains your test function, as in the following example:

$ deno test greeting_function_test.ts

If you're in the base directory for your project, run the command as follows:

$ deno test functions/greeting_function_test.ts

If you want to run all of your function tests, run the command without any file names as follows:

$ deno test

✨ For more information about Deno's built-in test runner, refer to testing.

Integrating a test into your CI/CD pipeline

For more information, refer to CI/CD pipeline setup.


Have 2 minutes to provide some feedback?

We'd love to hear about your experience building modular Slack apps. Please complete our short survey so we can use your feedback to improve.