概要
EmulateはVercel Labsが開発したサービスエミュレーター。Vercel、GitHub、Google、Slack、Apple、Microsoft、AWSなどの外部サービスをローカル環境で再現し、CI/CDパイプラインやネットワークが制限された環境でのテストを可能にする。本番環境に近い条件でのテスト実行により、デプロイ前のバグ検出と問題解決を加速させる仕組み。
主な機能
- 複数サービスのローカルエミュレーション:Vercel、GitHub、Google、Slack、Apple、Microsoft、AWSなど主要サービスをデフォルト設定で起動可能
- CLI インターフェース:コマンドラインから特定のサービス選択や設定の初期化が可能
- プログラマティックAPI:TypeScriptでの制御により、テストフレームワークへの統合が容易
- Vitest/Jest統合:既存のテストフレームワークとシームレスに連携
- 設定ファイル対応:YAML/JSON形式で柔軟な初期化が可能
- No-network環境対応:オフラインでの開発・テスト環境を実現
クイックスタート
インストール
Emulateはnpxで直接実行するか、プログラマティック利用時はnpmパッケージとして導入。
npx emulate
またはプログラマティックAPI使用時:
npm install emulate
基本的な実行方法
デフォルト設定で全サービスを起動:
npx emulate
起動時に以下のポートでサービスが利用可能になる:
- Vercel on
http://localhost:4000 - GitHub on
http://localhost:4001 - Google on
http://localhost:4002 - Slack on
http://localhost:4003 - Apple on
http://localhost:4004 - Microsoft on
http://localhost:4005 - AWS on
http://localhost:4006
CLIオプション
特定のサービスのみを起動する場合:
emulate --service vercel,github
カスタムポート指定:
emulate --port 3000
設定ファイルを使用:
emulate --seed config.yaml
設定ファイルの生成:
emulate init
特定サービスの設定生成:
emulate init --service vercel
利用可能なサービス一覧:
emulate list
CLIオプション一覧
| フラグ | デフォルト | 説明 |
|---|---|---|
-p, --port |
4000 |
基本ポート(サービスごとに自動加算) |
-s, --service |
all | カンマ区切りで指定するサービス |
--seed |
auto-detect | 設定ファイルパス(YAML/JSON) |
ポートは環境変数 EMULATE_PORT または PORT でも設定可能。
プログラマティックAPI
基本的な使用方法
import { createEmulator } from 'emulate'
const github = await createEmulator({ service: 'github', port: 4001 })
const vercel = await createEmulator({ service: 'vercel', port: 4002 })
github.url // 'http://localhost:4001'
vercel.url // 'http://localhost:4002'
await github.close()
await vercel.close()
Vitest / Jest統合
// vitest.setup.ts
import { createEmulator, type Emulator } from 'emulate'
let github: Emulator
let vercel: Emulator
beforeAll(async () => {
;[github, vercel] = await Promise.all([
createEmulator({ service: 'github', port: 4001 }),
createEmulator({ service: 'vercel', port: 4002 })
])
})
afterAll(async () => {
await Promise.all([github.close(), vercel.close()])
})
AWSサービス(SQS・S3)のエミュレーション
EmulateはAWSをローカルポート(デフォルト4006)でエミュレートする。AWS SDKの endpoint オプションにエミュレーターURLを指定するだけで、本番のIAM認証なしにS3やSQSのAPIを呼び出せる。
AWSエミュレーター単体起動
emulate --service aws
# → AWS on http://localhost:4006
SQSでキューメッセージの送受信をテスト
import { createEmulator } from 'emulate'
import { SQSClient, SendMessageCommand, ReceiveMessageCommand } from '@aws-sdk/client-sqs'
const aws = await createEmulator({ service: 'aws', port: 4006 })
const sqs = new SQSClient({
region: 'us-east-1',
endpoint: aws.url,
credentials: { accessKeyId: 'test', secretAccessKey: 'test' }
})
const queueUrl = `${aws.url}/000000000000/test-queue`
// メッセージ送信
await sqs.send(new SendMessageCommand({
QueueUrl: queueUrl,
MessageBody: JSON.stringify({ event: 'user.created', userId: '123' })
}))
// メッセージ受信
const { Messages } = await sqs.send(new ReceiveMessageCommand({
QueueUrl: queueUrl,
MaxNumberOfMessages: 10
}))
console.log(Messages)
await aws.close()
S3でファイルのアップロード・取得をテスト
import { createEmulator } from 'emulate'
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'
const aws = await createEmulator({ service: 'aws', port: 4006 })
const s3 = new S3Client({
region: 'us-east-1',
endpoint: aws.url,
forcePathStyle: true, // ローカルエミュレーターでは必須
credentials: { accessKeyId: 'test', secretAccessKey: 'test' }
})
// ファイルアップロード
await s3.send(new PutObjectCommand({
Bucket: 'test-bucket',
Key: 'uploads/report.json',
Body: JSON.stringify({ result: 'ok' }),
ContentType: 'application/json'
}))
// ファイル取得
const { Body } = await s3.send(new GetObjectCommand({
Bucket: 'test-bucket',
Key: 'uploads/report.json'
}))
const text = await Body?.transformToString()
console.log(text) // '{"result":"ok"}'
await aws.close()
Vitestでの統合テスト例:
// s3.test.ts
import { createEmulator, type Emulator } from 'emulate'
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'
let aws: Emulator
let s3: S3Client
beforeAll(async () => {
aws = await createEmulator({ service: 'aws', port: 4006 })
s3 = new S3Client({
region: 'us-east-1',
endpoint: aws.url,
forcePathStyle: true,
credentials: { accessKeyId: 'test', secretAccessKey: 'test' }
})
})
afterAll(() => aws.close())
test('S3にファイルをアップロードして取得できる', async () => {
await s3.send(new PutObjectCommand({
Bucket: 'test-bucket',
Key: 'test.txt',
Body: 'hello world'
}))
const { Body } = await s3.send(new GetObjectCommand({
Bucket: 'test-bucket',
Key: 'test.txt'
}))
expect(await Body?.transformToString()).toBe('hello world')
})
SlackエミュレーションでWebhookとAPIをテストする
EmulateはSlack APIをローカルポート(デフォルト4003)でエミュレートする。@slack/web-api や @slack/webhook SDKの接続先URLを差し替えるだけで、Incoming Webhook・Bolt・slash commandのテストがネットワーク不要で実行可能。
Slack Web APIでメッセージ送信をテスト
emulate --service slack
# → Slack on http://localhost:4003
import { createEmulator } from 'emulate'
import { WebClient } from '@slack/web-api'
const slack = await createEmulator({ service: 'slack', port: 4003 })
// slackApiUrl にエミュレーターURLを指定
const client = new WebClient('xoxb-test-token', {
slackApiUrl: `${slack.url}/api/`
})
const result = await client.chat.postMessage({
channel: '#general',
text: 'デプロイ完了! :rocket:',
blocks: [
{
type: 'section',
text: { type: 'mrkdwn', text: '*デプロイ完了*\n本番環境へのデプロイが成功しました。' }
}
]
})
console.log('Message sent, ts:', result.ts)
await slack.close()
Incoming Webhookのテスト
import { createEmulator } from 'emulate'
import { IncomingWebhook } from '@slack/webhook'
const slack = await createEmulator({ service: 'slack', port: 4003 })
// WebhookのURLをエミュレーターに向ける
const webhook = new IncomingWebhook(
`${slack.url}/services/T00000000/B00000000/XXXXXXXXXX`
)
await webhook.send({
text: 'CI/CDパイプラインが完了しました',
attachments: [
{
color: 'good',
fields: [
{ title: 'ブランチ', value: 'main', short: true },
{ title: 'ステータス', value: '成功', short: true }
]
}
]
})
await slack.close()
slash commandレスポンスのテスト
import { createEmulator, type Emulator } from 'emulate'
let slackEmulator: Emulator
beforeAll(async () => {
slackEmulator = await createEmulator({ service: 'slack', port: 4003 })
})
afterAll(() => slackEmulator.close())
test('slash commandに正しく応答する', async () => {
// エミュレーターにslash commandリクエストを直接送信
const response = await fetch(`${slackEmulator.url}/commands`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
command: '/deploy',
text: 'production',
user_id: 'U123456',
channel_id: 'C123456'
})
})
expect(response.ok).toBe(true)
})
AWSとSlackを組み合わせた統合テスト
SQSメッセージをトリガーにSlackへ通知するフローなど、複数サービスをまたぐ処理もEmulateで一括テストできる。
:4006 participant App as アプリケーション participant Slack as Slack Emulator
:4003 Test->>AWS: SQSメッセージ送信
(order.completed) AWS-->>App: メッセージ配信 App->>App: イベント処理 App->>Slack: chat.postMessage
(注文完了通知) Slack-->>Test: レスポンス確認 Test->>Test: アサーション
import { createEmulator, type Emulator } from 'emulate'
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs'
import { WebClient } from '@slack/web-api'
let aws: Emulator
let slack: Emulator
beforeAll(async () => {
;[aws, slack] = await Promise.all([
createEmulator({ service: 'aws', port: 4006 }),
createEmulator({ service: 'slack', port: 4003 })
])
})
afterAll(async () => {
await Promise.all([aws.close(), slack.close()])
})
test('SQSメッセージ受信後にSlack通知を送る', async () => {
const sqs = new SQSClient({
region: 'us-east-1',
endpoint: aws.url,
credentials: { accessKeyId: 'test', secretAccessKey: 'test' }
})
const slackClient = new WebClient('xoxb-test-token', {
slackApiUrl: `${slack.url}/api/`
})
// SQSにイベントを投入
await sqs.send(new SendMessageCommand({
QueueUrl: `${aws.url}/000000000000/events`,
MessageBody: JSON.stringify({ type: 'order.completed', orderId: '456' })
}))
// Slack通知関数を呼び出してテスト
const result = await slackClient.chat.postMessage({
channel: '#orders',
text: '注文 #456 が完了しました'
})
expect(result.ok).toBe(true)
})
エミュレーター対応サービス比較
| サービス | ポート | 主なエミュレート対象 | 代表的なSDK |
|---|---|---|---|
| AWS | 4006 | SQS、S3、DynamoDB等 | @aws-sdk/client-sqs, @aws-sdk/client-s3 |
| Slack | 4003 | Web API、Webhook、Events API | @slack/web-api, @slack/webhook |
| GitHub | 4001 | REST API、Webhooks | @octokit/rest |
| Vercel | 4000 | Deployments API | @vercel/client |
| 4002 | OAuth、Sheets、Drive等 | googleapis |
ユースケース
ローカル開発での外部API呼び出しのテスト
GitHub APIやVercel APIを呼び出すアプリケーションの開発時、ローカルエミュレーターを使用してネットワーク接続なしでテスト実行が可能。
CI/CDパイプラインでのネットワーク分離環境対応
CI環境がネットワークアクセスを制限している場合、Emulateでサービスをローカルエミュレートしてテストスイートを実行。
複数サービス統合のテスト
複数の外部サービス連携を扱うアプリケーションで、それぞれのサービスを同時にエミュレートして統合テストを実施。
アーキテクチャ
Emulateは各サービスをローカルポート上で独立したプロセスとして実行。アプリケーションはローカルホストの指定ポートにリクエストを送信することで、本番の外部サービスと同じインターフェースで通信可能。
:4000"] App --> G["GitHub Emulator
:4001"] App --> Go["Google Emulator
:4002"] App --> S["Slack Emulator
:4003"] App --> A["AWS Emulator
:4006"] V --> Impl["ステートフルなAPI実装
(Emulate コア)"] G --> Impl Go --> Impl S --> Impl A --> Impl
設定方法
YAML形式の設定例:
services:
- name: vercel
port: 4000
- name: github
port: 4001
JSON形式の設定例:
{
"services": [
{"name": "vercel", "port": 4000},
{"name": "github", "port": 4001}
]
}
利点と注意点
Emulateは本番環境に近いAPI動作を提供するため、外部サービスの実装詳細をテストできる。一方、サービスの仕様変更時にはエミュレーターも同期更新が必要となる。また、全機能がエミュレートされているかは各サービスのドキュメントで確認が必要。
まとめ
EmulateはCI/CDパイプラインやローカル開発環境で外部サービスの呼び出しをテストする際に有用なツール。ネットワークが制限された環境での開発や、複数サービス統合の検証が必要なプロジェクトに適している。設定ファイルなしでもデフォルト設定で即座に主要サービスをエミュレート可能であり、段階的な導入が容易。
LocalStackとの違い:どちらを選ぶべきか
LocalStackはAWSサービスのローカルエミュレーターとして長年の実績を持つOSSで、80以上のAWSサービスに対応する。一方、Vercel Emulateは「AWS専用」ではなく、Vercel・GitHub・Slack・Google・MicrosoftなどマルチクラウドをDocker不要で即起動できる点が特徴だ。LocalStack代替として検討する際の比較ポイントをまとめる。
Vercel Emulate vs LocalStack 比較表
| 項目 | Vercel Emulate | LocalStack |
|---|---|---|
| セットアップ | npx emulate で即起動(設定不要) |
Docker + compose設定が必要 |
| 対応サービス数 | AWS含む主要7サービス(Vercel/GitHub/Google/Slack/Apple/Microsoft/AWS) | AWS 80+サービス(S3/SQS/Lambda/DynamoDB/RDS等) |
| Docker依存 | なし(Node.jsのみ) | 必須(Docker Desktopが必要) |
| メモリ使用量 | 軽量(数十MB程度) | 重量(数百MB〜1GB以上) |
| ユースケース | 軽量テスト・CI環境・マルチサービス統合 | 本格AWSインフラテスト・IaCの検証 |
| 料金 | OSS(完全無料) | OSS無料 + LocalStack Pro(有料プランあり) |
| CI/CD統合 | 設定ゼロで動作・GitHub Actions対応 | Docker-in-Dockerの設定が必要なケースあり |
| AWSカバレッジ | 基本的なSQS/S3/DynamoDB相当 | Lambda・ECS・RDS等の高度なサービスも対応 |
本格インフラ"| B["LocalStack
(Lambda/RDS/ECS等が必要)"] A -->|"AWS + GitHub/Slack等
マルチサービス"| C["Vercel Emulate
(軽量・Docker不要)"] B --> D["LocalStack Pro検討
(高度なAWSサービス)"] C --> E["npx emulateで即スタート"]
LocalStackから乗り換えるべきケース
以下の条件に当てはまる場合、Vercel Emulateへの移行がLocalStack代替として有効な選択肢になる。
Vercel Emulateを選ぶべき状況:
- Docker環境を持たないCI/CD:GitHub ActionsのデフォルトランナーはDockerを内包するが、軽量ランナーや制限環境ではDockerが使えない。Emulateは
npx一発で起動できるため、Dockerの有無を問わない - AWSだけでなくSlack/GitHubも統合テストしたい:1つのツールでマルチサービスのエミュレーションが完結する
- チームがNode.jsに慣れており、TypeScript APIで細かく制御したい:
createEmulator()によるプログラマティックなセットアップが容易 - 立ち上げ速度を重視する場合:LocalStackはDockerイメージのPullや起動に時間がかかる。EmulateはNode.jsプロセスとして軽量起動
# LocalStack(Docker必須)
docker run --rm -p 4566:4566 localstack/localstack
# Vercel Emulate(Docker不要・即起動)
npx emulate --service aws
LocalStackのままでいいケース
LocalStack代替を検討しても、以下のケースではLocalStackの継続利用が適切だ。
LocalStackを維持すべき状況:
- Lambda関数・ECS・API Gatewayなど高度なAWSサービスが必要:EmulateのAWSカバレッジはS3/SQS/DynamoDB相当に留まる。Lambdaのローカル実行やStep Functionsのワークフローテストにはまだ対応していない
- Terraform/CDK/CloudFormationでIaCを検証したい:LocalStackはインフラコードの実行環境としての実績が厚い
- 既存のLocalStackテストスイートが大規模で移行コストが高い:テストの書き直しが必要になるため、既存資産が多い場合は慎重な評価が必要
- LocalStack Proで商用サポートが必要な場合:エンタープライズ環境での保証が必要な場合はLocalStack Proを検討
LocalStack比較まとめ:EmulateはAWSに特化した深いエミュレーションより「素早くマルチサービスをモックしてCI/CDを回したい」ニーズにフィットする。LocalStack代替を探しているなら、まずユースケースの範囲を確認してから選択するのが賢明だ。