메인 콘텐츠로 건너뛰기
SDK는 테스트 코드에서 앱을 빌드, 배포, 설치 및 제거할 수 있는 프로그래매틱 API를 제공합니다. 이것을 Vitest 및 타입이 지정된 API 클라이언트와 함께 사용하면 실제 Twenty 서버를 대상으로 앱이 종단 간으로 동작하는지 검증하는 통합 테스트를 작성할 수 있습니다.

npm 패키지 사용

앱에서 원하는 npm 패키지를 설치해 사용할 수 있습니다. 로직 함수와 프런트 컴포넌트는 모두 esbuild로 번들되며, 모든 의존성이 출력물에 인라인됩니다 — 런타임에는 node_modules가 필요하지 않습니다.

패키지 설치

yarn add axios
그런 다음 코드에서 임포트하세요:
src/logic-functions/fetch-data.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import axios from 'axios';

const handler = async (): Promise<any> => {
  const { data } = await axios.get('https://api.example.com/data');

  return { data };
};

export default defineLogicFunction({
  universalIdentifier: '...',
  name: 'fetch-data',
  description: 'Fetches data from an external API',
  timeoutSeconds: 10,
  handler,
});
프런트 컴포넌트에서도 동일하게 작동합니다:
src/front-components/chart.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { format } from 'date-fns';

const DateWidget = () => {
  return <p>Today is {format(new Date(), 'MMMM do, yyyy')}</p>;
};

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'date-widget',
  component: DateWidget,
});

번들링 동작 방식

빌드 단계에서는 esbuild를 사용하여 로직 함수와 프런트 컴포넌트마다 단일 자체 포함 파일을 생성합니다. 모든 임포트된 패키지는 번들에 인라인됩니다. 로직 함수는 Node.js 환경에서 실행됩니다. Node 기본 모듈(fs, path, crypto, http 등) 을(를) 사용할 수 있으며 설치할 필요가 없습니다. 프런트 컴포넌트는 Web Worker에서 실행됩니다. Node 기본 모듈은 사용할 수 없습니다 — 브라우저 환경에서 동작하는 브라우저 API와 npm 패키지만 사용할 수 있습니다. 두 환경 모두에서 twenty-client-sdk/coretwenty-client-sdk/metadata가 사전 제공 모듈로 사용 가능합니다 — 이는 번들되지 않고 서버가 런타임에 해석합니다.

설정

스캐폴딩된 앱에는 이미 Vitest가 포함되어 있습니다. 수동으로 설정하는 경우, 의존성을 설치하세요:
yarn add -D vitest vite-tsconfig-paths
앱 루트에 vitest.config.ts를 생성하세요:
vitest.config.ts
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  plugins: [
    tsconfigPaths({
      projects: ['tsconfig.spec.json'],
      ignoreConfigErrors: true,
    }),
  ],
  test: {
    testTimeout: 120_000,
    hookTimeout: 120_000,
    include: ['src/**/*.integration-test.ts'],
    setupFiles: ['src/__tests__/setup-test.ts'],
    env: {
      TWENTY_API_URL: 'http://localhost:2020',
      TWENTY_API_KEY: 'your-api-key',
    },
  },
});
테스트 실행 전에 서버에 접근 가능한지 확인하는 설정 파일을 생성하세요:
src/__tests__/setup-test.ts
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { beforeAll } from 'vitest';

const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');

beforeAll(async () => {
  // Verify the server is running
  const response = await fetch(`${TWENTY_API_URL}/healthz`);

  if (!response.ok) {
    throw new Error(
      `Twenty server is not reachable at ${TWENTY_API_URL}. ` +
        'Start the server before running integration tests.',
    );
  }

  // Write a temporary config for the SDK
  fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });

  fs.writeFileSync(
    path.join(TEST_CONFIG_DIR, 'config.json'),
    JSON.stringify({
      remotes: {
        local: {
          apiUrl: process.env.TWENTY_API_URL,
          apiKey: process.env.TWENTY_API_KEY,
        },
      },
      defaultRemote: 'local',
    }, null, 2),
  );
});

프로그래매틱 SDK API

twenty-sdk/cli 서브 경로는 테스트 코드에서 직접 호출할 수 있는 함수를 내보냅니다:
함수설명
appBuild앱을 빌드하고 필요하면 타르볼로 패키징
appDeploy타르볼을 서버로 업로드
appInstall활성 워크스페이스에 앱 설치
appUninstall활성 워크스페이스에서 앱 제거
각 함수는 success: booleandata 또는 error를 포함한 결과 객체를 반환합니다.

통합 테스트 작성

다음은 앱을 빌드, 배포, 설치한 후 워크스페이스에 표시되는지 검증하는 전체 예제입니다:
src/__tests__/app-install.integration-test.ts
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const APP_PATH = process.cwd();

describe('App installation', () => {
  beforeAll(async () => {
    const buildResult = await appBuild({
      appPath: APP_PATH,
      tarball: true,
      onProgress: (message: string) => console.log(`[build] ${message}`),
    });

    if (!buildResult.success) {
      throw new Error(`Build failed: ${buildResult.error?.message}`);
    }

    const deployResult = await appDeploy({
      tarballPath: buildResult.data.tarballPath!,
      onProgress: (message: string) => console.log(`[deploy] ${message}`),
    });

    if (!deployResult.success) {
      throw new Error(`Deploy failed: ${deployResult.error?.message}`);
    }

    const installResult = await appInstall({ appPath: APP_PATH });

    if (!installResult.success) {
      throw new Error(`Install failed: ${installResult.error?.message}`);
    }
  });

  afterAll(async () => {
    await appUninstall({ appPath: APP_PATH });
  });

  it('should find the installed app in the workspace', async () => {
    const metadataClient = new MetadataApiClient();

    const result = await metadataClient.query({
      findManyApplications: {
        id: true,
        name: true,
        universalIdentifier: true,
      },
    });

    const installedApp = result.findManyApplications.find(
      (app: { universalIdentifier: string }) =>
        app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
    );

    expect(installedApp).toBeDefined();
  });
});

테스트 실행

로컬 Twenty 서버가 실행 중인지 확인한 다음, 다음을 실행하세요:
yarn test
또는 개발 중에는 워치 모드로 실행하세요:
yarn test:watch

타입 검사

테스트를 실행하지 않고도 앱에 대해 타입 검사를 수행할 수 있습니다:
yarn twenty dev:typecheck
이는 tsc --noEmit를 실행하고 모든 타입 오류를 보고합니다.

GitHub Actions로 CI

스캐폴더가 .github/workflows/ci.yml에 바로 사용할 수 있는 GitHub Actions 워크플로를 생성합니다. main으로의 푸시와 풀 리퀘스트마다 통합 테스트를 자동으로 실행합니다. 워크플로:
  1. 코드를 체크아웃합니다
  2. twentyhq/twenty/.github/actions/spawn-twenty-docker-image 액션을 사용해 임시 Twenty 서버를 구동합니다
  3. yarn install --immutable로 종속성을 설치합니다
  4. 액션 출력에서 주입된 TWENTY_API_URLTWENTY_API_KEYyarn test를 실행합니다
.github/workflows/ci.yml
name: CI

on:
  push:
    branches:
      - main
  pull_request: {}

env:
  TWENTY_VERSION: latest

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Spawn Twenty instance
        id: twenty
        uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
        with:
          twenty-version: ${{ env.TWENTY_VERSION }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Enable Corepack
        run: corepack enable

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --immutable

      - name: Run integration tests
        run: yarn test
        env:
          TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
          TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
별도의 시크릿을 구성할 필요가 없습니다 — spawn-twenty-docker-image 액션이 러너 내에서 일시적인 Twenty 서버를 직접 시작하고 연결 정보를 출력합니다. GITHUB_TOKEN 시크릿은 GitHub에서 자동으로 제공됩니다. latest 대신 특정 Twenty 버전을 고정하려면 워크플로 상단의 TWENTY_VERSION 환경 변수를 변경하세요.