Vendure開発ハンドブック¶
このドキュメントは、Vendureでの開発に必要なコアコンセプトと高度な実装パターンを体系的にまとめた知識ベースです。プロジェクト固有のセットアップについては Vendure開発ガイド を参照してください。
目次¶
アーキテクチャ概要¶
ヘッドレスアーキテクチャとGraphQL¶
Vendureは完全なヘッドレスアーキテクチャを採用しており、すべての機能はGraphQL APIを通じて提供されます。
2つのAPIエンドポイント¶
- Shop API (
/shop-api): ストアフロント(顧客向け)用のAPI - 認証: Customer認証(Cookie/Bearer Token)
- 用途: 商品閲覧、カート操作、注文作成
-
権限: 顧客がアクセス可能なデータのみ
-
Admin API (
/admin-api): 管理画面(管理者向け)用のAPI - 認証: Administrator認証(Cookie/Bearer Token)
- 用途: 商品管理、注文管理、顧客管理、設定変更
- 権限: Role-Based Access Control (RBAC)
GraphQLスキーマの自動生成¶
Vendureはプラグインで定義されたGraphQLスキーマを自動的に統合し、単一のスキーマとして提供します。
import { gql } from 'graphql-tag';
const myGraphqlSchema = gql`
extend type Query {
myQuery: String!
}
extend type Mutation {
myMutation(input: String!): Boolean!
}
`;
@VendurePlugin({
shopApiExtensions: {
schema: myGraphqlSchema,
resolvers: [MyResolver],
},
})
export class MyPlugin {}
オニオンアーキテクチャとサービスレイヤーパターン¶
Vendureはオニオンアーキテクチャ(Onion Architecture)を採用し、依存関係の方向を明確にしています。
レイヤー構造¶
┌─────────────────────────────────────┐
│ GraphQL Resolvers (API Layer) │
├─────────────────────────────────────┤
│ Services (Business Logic) │
├─────────────────────────────────────┤
│ Entities (Domain Models) │
├─────────────────────────────────────┤
│ TypeORM (Data Access) │
└─────────────────────────────────────┘
依存関係の原則¶
- 外側から内側への依存: Resolver → Service → Entity
- 内側から外側への依存は禁止: EntityがServiceやResolverに依存しない
- DIコンテナ: NestJSの依存性注入システムを使用
プラグインシステムの全体像¶
Vendureのすべての機能はプラグインとして実装されます。プラグインは以下の要素で構成されます:
プラグインの構成要素¶
@VendurePlugin({
// 1. エンティティ定義
entities: [MyCustomEntity],
// 2. サービス・プロバイダー
imports: [PluginCommonModule],
providers: [MyService],
// 3. GraphQL API拡張
shopApiExtensions: {
schema: shopSchema,
resolvers: [ShopResolver],
},
adminApiExtensions: {
schema: adminSchema,
resolvers: [AdminResolver],
},
// 4. 設定の拡張
configuration: config => {
config.customFields.Customer.push({
/* ... */
});
return config;
},
})
export class MyPlugin {}
コアコンセプト詳細¶
Request Context (ctx)¶
RequestContextは、すべてのVendure操作の中心となるオブジェクトです。リクエストごとに作成され、言語、チャネル、ユーザー情報などのコンテキストを保持します。
RequestContextの主要プロパティ¶
interface RequestContext {
// チャネル情報
channel: Channel;
channelId: ID;
// 言語情報
languageCode: LanguageCode;
// ユーザー情報
user?: User;
activeUserId?: ID;
// 認証情報
isAuthorized: boolean;
apiType: 'shop' | 'admin';
// セッション情報
session?: Session;
sessionCache?: SessionCache;
}
RequestContextの使用方法¶
すべてのサービスメソッドは、最初の引数としてRequestContextを受け取ります:
@Injectable()
export class MyService {
constructor(private connection: TransactionalConnection) {}
async getData(ctx: RequestContext, id: string): Promise<MyEntity> {
// ctx.channelId でチャネルを識別
// ctx.languageCode で言語を識別
// ctx.user でユーザー情報を取得
return this.connection
.getRepository(ctx, MyEntity)
.findOne({ where: { id, channelId: ctx.channelId } });
}
}
RequestContextのライフサイクル¶
- リクエスト受信: HTTPリクエストが到着
- Context作成: チャネル、言語、ユーザー情報から
RequestContextを生成 - Resolver実行: GraphQL Resolverに
ctxが渡される - Service呼び出し: Serviceメソッドに
ctxが渡される - トランザクション管理:
ctxに紐づくトランザクションが管理される
トランザクション管理¶
RequestContextは、リクエスト全体を通じて同じデータベーストランザクションを共有します:
async function processOrder(ctx: RequestContext, orderId: string) {
// すべての操作が同じトランザクション内で実行される
const order = await this.orderService.findOne(ctx, orderId);
await this.paymentService.process(ctx, order);
await this.fulfillmentService.create(ctx, order);
// エラーが発生した場合、すべてロールバックされる
}
Event Bus¶
Vendureはイベント駆動アーキテクチャを採用しており、EventBusを通じて非同期処理や拡張ポイントを提供します。
イベントの種類¶
- 同期イベント: リクエスト処理中に同期的に発火(例:
OrderPlacedEvent) - 非同期イベント: バックグラウンドで処理(例:
OrderStateTransitionEvent)
イベントの購読¶
import { EventBus, OrderPlacedEvent } from '@vendure/core';
@Injectable()
export class MyService {
constructor(private eventBus: EventBus) {
// イベントの購読
this.eventBus.ofType(OrderPlacedEvent).subscribe(event => {
console.log('Order placed:', event.entity.id);
// カスタム処理を実行
});
}
}
カスタムイベントの作成¶
import { VendureEvent } from '@vendure/core';
export class CustomOrderEvent extends VendureEvent {
constructor(
public ctx: RequestContext,
public order: Order,
public customData: any,
) {
super();
}
}
// イベントの発火
this.eventBus.publish(new CustomOrderEvent(ctx, order, data));
イベントのベストプラクティス¶
- 重い処理は非同期: メール送信、外部API呼び出しなどは非同期イベントを使用
- エラーハンドリング: イベントハンドラ内のエラーは適切にキャッチ
- 順序保証: イベントの処理順序は保証されないため、依存関係に注意
Worker System¶
Vendureは非同期ジョブキューシステムを提供し、重い処理をバックグラウンドで実行できます。
Job Queue Plugin¶
// vendure-config.ts
import { DefaultJobQueuePlugin } from '@vendure/core';
export const config: VendureConfig = {
plugins: [
DefaultJobQueuePlugin.init({
pollInterval: 2000, // ポーリング間隔(ミリ秒)
}),
],
};
ジョブの作成と実行¶
import { JobQueue, JobQueueService } from '@vendure/core';
@Injectable()
export class MyService {
constructor(private jobQueueService: JobQueueService) {}
async processLargeData(ctx: RequestContext, data: any) {
// ジョブをキューに追加
const job = await this.jobQueueService.add({
name: 'process-large-data',
data: { dataId: data.id },
retries: 3,
});
return job;
}
}
カスタムジョブプロセッサー¶
import { Job, JobQueue, JobQueueService } from '@vendure/core';
@Injectable()
export class MyJobProcessor {
async process(job: Job): Promise<void> {
const { dataId } = job.data;
// 重い処理を実行
await this.processData(dataId);
}
}
// プラグインで登録
@VendurePlugin({
providers: [MyJobProcessor],
jobQueueOptions: {
process: async job => {
const processor = app.get(MyJobProcessor);
return processor.process(job);
},
},
})
export class MyPlugin {}
Strategies¶
VendureはConfigurableOperationDefを使用して、特定のロジックを差し替え可能にする「Strategy」パターンを提供します。
Strategyの例: Shipping Calculator¶
import { ShippingCalculator, ShippingCalculatorArgs } from '@vendure/core';
export class CustomShippingCalculator implements ShippingCalculator {
code = 'custom-shipping';
description = [{ languageCode: LanguageCode.ja, value: 'カスタム配送計算' }];
calculate(
ctx: RequestContext,
args: ShippingCalculatorArgs,
): number | Promise<number> {
// カスタム配送料計算ロジック
const basePrice = 500;
const weight = args.orderLines.reduce(
(sum, line) => sum + line.productVariant.weight,
0,
);
return basePrice + weight * 100;
}
}
// 設定で登録
export const config: VendureConfig = {
shippingOptions: {
shippingCalculators: [CustomShippingCalculator],
},
};
Strategyの実装パターン¶
- インターフェース実装:
ShippingCalculator、PaymentMethodHandlerなどのインターフェースを実装 - 設定で登録:
vendure-config.tsでStrategyを登録 - 動的選択: 管理者がDashboardでStrategyを選択可能
データアクセスと拡張¶
TransactionalConnection¶
TransactionalConnectionは、Vendureが提供するトランザクションセーフなデータベースアクセス層です。
基本的な使用方法¶
import { TransactionalConnection } from '@vendure/core';
@Injectable()
export class MyService {
constructor(private connection: TransactionalConnection) {}
async getEntity(ctx: RequestContext, id: string): Promise<MyEntity> {
// Repositoryを取得
const repo = this.connection.getRepository(ctx, MyEntity);
// クエリ実行
return repo.findOne({ where: { id } });
}
async createEntity(
ctx: RequestContext,
data: Partial<MyEntity>,
): Promise<MyEntity> {
const repo = this.connection.getRepository(ctx, MyEntity);
const entity = repo.create(data);
return repo.save(entity);
}
}
トランザクションの明示的管理¶
async function complexOperation(ctx: RequestContext) {
// 新しいトランザクションを開始
return this.connection.withTransaction(ctx, async transactionCtx => {
const entity1 = await this.createEntity1(transactionCtx, data1);
const entity2 = await this.createEntity2(transactionCtx, data2);
// エラーが発生した場合、すべてロールバック
return { entity1, entity2 };
});
}
クエリビルダーの使用¶
async function complexQuery(ctx: RequestContext) {
const repo = this.connection.getRepository(ctx, Order);
return repo
.createQueryBuilder('order')
.leftJoinAndSelect('order.lines', 'line')
.leftJoinAndSelect('line.productVariant', 'variant')
.where('order.state = :state', { state: 'PaymentSettled' })
.andWhere('order.createdAt > :date', { date: new Date('2024-01-01') })
.getMany();
}
Custom Fields¶
Custom Fieldsを使用すると、Vendureの標準エンティティに独自のフィールドを追加できます。
Custom Fieldsの定義¶
// vendure-config.ts
export const config: VendureConfig = {
customFields: {
Customer: [
{
name: 'customerStatus',
type: 'string',
defaultValue: 'general',
label: [{ languageCode: LanguageCode.ja, value: '顧客ステータス' }],
options: [
{
value: 'general',
label: [{ languageCode: LanguageCode.ja, value: '一般' }],
},
{
value: 'premium',
label: [{ languageCode: LanguageCode.ja, value: 'プレミアム' }],
},
],
},
{
name: 'discountRate',
type: 'float',
defaultValue: 0,
min: 0,
max: 100,
},
],
Product: [
{
name: 'minimumOrderQuantity',
type: 'int',
defaultValue: 1,
},
],
},
};
プラグインでのCustom Fields追加¶
@VendurePlugin({
configuration: config => {
config.customFields.Customer = config.customFields.Customer ?? [];
config.customFields.Customer.push({
name: 'customField',
type: 'string',
});
return config;
},
})
export class MyPlugin {}
TypeScript型定義¶
Custom Fieldsを使用する場合、TypeScriptの型定義を拡張する必要があります:
// src/types/vendure-custom-fields.ts
declare module '@vendure/core' {
interface CustomCustomerFields {
customerStatus?: string;
discountRate?: number;
}
interface CustomProductFields {
minimumOrderQuantity?: number;
}
}
Custom Fieldsの使用¶
// 読み取り
const customer = await this.customerService.findOne(ctx, customerId);
const status = customer.customFields.customerStatus;
// 更新
await this.customerService.update(ctx, customerId, {
customFields: {
customerStatus: 'premium',
discountRate: 10,
},
});
Entity Translation¶
Vendureは多言語対応エンティティをサポートしており、Translationエンティティを使用して複数言語のデータを管理します。
翻訳可能なエンティティ¶
ProductProductVariantCollectionFacetFacetValueShippingMethodPaymentMethodPromotion
翻訳データの作成¶
await this.productService.create(ctx, {
translations: [
{
languageCode: LanguageCode.ja,
name: '商品名(日本語)',
description: '説明(日本語)',
},
{
languageCode: LanguageCode.en,
name: 'Product Name (English)',
description: 'Description (English)',
},
],
});
翻訳データの取得¶
// RequestContextのlanguageCodeに基づいて自動的に翻訳が選択される
const product = await this.productService.findOne(ctx, productId);
// product.name は ctx.languageCode に応じた翻訳が返される
// 特定の言語の翻訳を取得
const japaneseTranslation = product.translations.find(
t => t.languageCode === LanguageCode.ja,
);
プラグイン開発実践¶
ライフサイクルフック¶
プラグインは、Vendureの起動・停止時に実行されるライフサイクルフックを提供します。
利用可能なフック¶
@VendurePlugin({})
export class MyPlugin {
// プラグインの初期化時(サーバー起動時)
static async onBootstrap(app: INestApplication): Promise<void> {
// 初期化処理
}
// Workerプロセスの作成時
static async onWorkerCreation(app: INestApplication): Promise<void> {
// Worker固有の初期化処理
}
// プラグインの終了時
static async onClose(app: INestApplication): Promise<void> {
// クリーンアップ処理
}
}
実装例¶
@VendurePlugin({})
export class MyPlugin {
static async onBootstrap(app: INestApplication): Promise<void> {
const configService = app.get(ConfigService);
const logger = app.get(Logger);
logger.log('MyPlugin initialized', 'MyPlugin');
// 外部サービスへの接続など
await this.connectToExternalService(configService);
}
static async onClose(app: INestApplication): Promise<void> {
// 接続の切断など
await this.disconnectFromExternalService();
}
}
依存性注入 (DI) のベストプラクティス¶
VendureはNestJSの依存性注入システムを使用します。
プロバイダーの登録¶
@VendurePlugin({
imports: [PluginCommonModule], // Vendureの共通モジュール
providers: [MyService, MyRepository],
})
export class MyPlugin {}
サービスの注入¶
@Injectable()
export class MyService {
constructor(
private connection: TransactionalConnection,
private eventBus: EventBus,
private logger: Logger,
) {}
}
スコープ管理¶
- Singleton (デフォルト): アプリケーション全体で1つのインスタンス
- Request: リクエストごとに新しいインスタンス
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
// リクエストごとに新しいインスタンスが作成される
}
プラグインの構造¶
推奨ディレクトリ構造¶
packages/plugins/my-plugin/
├── src/
│ ├── index.ts # プラグイン定義
│ ├── entities/ # カスタムエンティティ
│ │ └── my-entity.entity.ts
│ ├── services/ # ビジネスロジック
│ │ └── my.service.ts
│ ├── api/ # GraphQL Resolver
│ │ ├── my.resolver.ts
│ │ └── my.schema.ts
│ └── types.ts # 型定義
└── package.json
実装例¶
// src/index.ts
import { PluginCommonModule, VendurePlugin } from '@vendure/core';
import { MyEntity } from './entities/my-entity.entity';
import { MyService } from './services/my.service';
import { MyResolver } from './api/my.resolver';
import { myGraphqlSchema } from './api/my.schema';
@VendurePlugin({
imports: [PluginCommonModule],
entities: [MyEntity],
providers: [MyService],
shopApiExtensions: {
schema: myGraphqlSchema,
resolvers: [MyResolver],
},
compatibility: '^3.5.0',
})
export class MyPlugin {}
Dashboard開発 (React)¶
Vendure DashboardはReactベースの管理画面です。旧Admin UI(Angular)は使用せず、Dashboardのみを使用します。
基本構造¶
Dashboard Pluginの設定¶
// vendure-config.ts
import { DashboardPlugin } from '@vendure/dashboard/plugin';
import { join } from 'node:path';
export const config: VendureConfig = {
plugins: [
DashboardPlugin.init({
route: 'dashboard',
appDir: join(__dirname, '../dist/dashboard'),
}),
],
};
ディレクトリ構造¶
apps/vendure-server/
├── src/
│ ├── plugins/
│ │ └── ritsubi-admin-extensions/
│ │ └── dashboard/ # カスタムDashboard拡張
│ │ ├── index.tsx # Dashboard拡張のエントリーポイント
│ │ ├── components/ # カスタムコンポーネント
│ │ ├── utils/ # 共通ユーティリティ
│ │ │ └── download-helper.ts # ファイルダウンロードヘルパー
│ │ └── i18n/ # 翻訳ファイル
│ │ ├── ja.po
│ │ └── en.po
│ ├── dashboard/ # カスタムDashboard拡張(旧構成、移行予定)
│ │ ├── index.tsx
│ │ └── i18n/
│ │ ├── ja.po
│ │ └── en.po
│ └── vendure-config.ts
├── lingui.config.js # Lingui設定
└── vite.config.mts # Vite設定
拡張方法¶
ページブロックの追加¶
既存のページにカスタムブロックを追加できます:
// src/dashboard/index.tsx
import { defineDashboardExtension } from '@vendure/dashboard';
import { CustomerPasswordAdminBlock } from './components/customer-password-admin';
export default defineDashboardExtension({
pageBlocks: [
{
id: 'customer-password-admin',
title: '初期パスワード設定',
location: {
pageId: 'customer-detail',
column: 'side',
position: { blockId: 'groups', order: 'after' },
},
component: CustomerPasswordAdminBlock,
shouldRender: context => Boolean(context.entity?.id),
requiresPermission: ['UpdateCustomer'],
},
],
});
新しいページの追加¶
export default defineDashboardExtension({
routes: [
{
path: '/my-custom-page',
component: () => <MyCustomPage />,
loader: () => ({ breadcrumb: 'カスタムページ' }),
navMenuItem: {
sectionId: 'settings',
title: 'カスタム設定',
},
},
],
});
ナビゲーションメニューの追加¶
export default defineDashboardExtension({
navSections: [
{
id: 'integrations',
title: '連携',
icon: Cable, // lucide-reactのアイコン
},
],
routes: [
{
path: '/smile-sync',
component: () => <SmileSyncPage />,
navMenuItem: {
sectionId: 'integrations',
title: 'SMILE連携',
},
},
],
});
公式UIコンポーネント¶
Dashboard拡張では、@vendure/dashboardの公式UIコンポーネントを優先的に使用します。
利用可能なコンポーネント¶
import {
Button,
Input,
Select,
Card,
Table,
Form,
Badge,
Dialog,
// ... その他多数
} from '@/vdb/components/ui/...';
レイアウトコンポーネント¶
import {
Page,
PageTitle,
PageLayout,
PageBlock,
} from '@/vdb/framework/...';
export function MyCustomPage() {
return (
<Page>
<PageTitle>カスタムページ</PageTitle>
<PageLayout>
<PageBlock>
{/* コンテンツ */}
</PageBlock>
</PageLayout>
</Page>
);
}
国際化 (i18n)¶
Dashboard拡張では、LinguiJSを使用して国際化を実装します。
翻訳キーの定義¶
import { Trans, useLingui } from '@lingui/react/macro';
export function MyComponent() {
const { t } = useLingui();
return (
<div>
<Trans>こんにちは</Trans>
<p>{t`現在の日時: ${new Date().toLocaleString()}`}</p>
</div>
);
}
翻訳ファイルの管理¶
# 翻訳キーの抽出
npx lingui extract
# 翻訳のコンパイル
npx lingui compile
翻訳ファイルは src/dashboard/i18n/ に配置されます:
src/dashboard/i18n/
├── ja.po
└── en.po
翻訳ファイルの編集¶
# ja.po
msgid "Hello"
msgstr "こんにちは"
msgid "Current time: {time}"
msgstr "現在の日時: {time}"
データ連携¶
GraphQL APIの使用¶
Dashboard拡張では、@vendure/dashboardが提供するapiオブジェクトを使用してGraphQL
APIにアクセスします。
import { api } from '@/vdb/graphql/api';
const query = /* GraphQL */ `
query GetCustomers($options: CustomerListOptions) {
customers(options: $options) {
items {
id
emailAddress
firstName
lastName
}
totalItems
}
}
`;
const result = await api.query(query, { options: { take: 10 } });
React Queryの使用¶
Dashboard拡張では、React Queryを使用してデータフェッチを管理します。
import { useQuery, useMutation } from '@tanstack/react-query';
import { api } from '@/vdb/graphql/api';
export function MyComponent() {
const { data, isLoading } = useQuery({
queryKey: ['customers'],
queryFn: () => api.query(customersQuery, {}),
});
const { mutateAsync } = useMutation({
mutationFn: variables => api.mutate(updateCustomerMutation, variables),
});
// ...
}
共通ユーティリティ¶
ファイルダウンロードヘルパー¶
Dashboard拡張でファイルダウンロード機能(CSVエクスポート、PDF生成等)を実装する際は、共通ユーティリティ関数を使用すること。
場所: apps/vendure-server/src/plugins/ritsubi-admin-extensions/dashboard/utils/download-helper.ts
提供関数:
downloadBlob(blob: Blob, filename: string): BlobデータをダウンロードdownloadBase64(base64: string, filename: string, mimeType: string): Base64文字列をデコードしてダウンロードdownloadText(content: string, filename: string, mimeType?: string): テキストデータをダウンロード
使用例:
import { downloadBase64, downloadText } from './utils/download-helper.js';
// GraphQLからBase64エンコードされたCSVデータを受け取る場合
const response = await api.mutate(exportMutation, variables);
downloadBase64(
response.exportSmileOrders.csvData,
response.exportSmileOrders.filename,
'text/csv'
);
// テキストデータ(CSV文字列など)を直接ダウンロードする場合
const csv = toCsv([header, ...rows]);
downloadText(csv, 'export.csv', 'text/csv');
重要な原則:
- DOM操作の一元化:
document.createElement('a')やdocument.body.appendChild()などのDOM操作は、このヘルパー関数内に集約されている。コンポーネント内で直接DOM操作を行わないこと。 - Vendure標準の不在: VendureのDashboardライブラリには、ファイルダウンロード用の標準ユーティリティが提供されていないため、プロジェクト固有の実装としてこのヘルパーを使用する。
- ブラウザ互換性: Firefox等の互換性のために
document.body.appendChildを使用し、確実にクリーンアップ(removeChild、URL.revokeObjectURL)を行う実装となっている。
実装の背景:
Vendureはヘッドレスコマースフレームワークとして、フロントエンドの実装方法に対して中立な立場を取るため、特定のブラウザAPI(DOM操作など)に依存するヘルパーをCoreに含めない方針です。そのため、ファイルダウンロード機能が必要な場合は、プロジェクト固有のユーティリティとして実装する必要があります。
テスト戦略¶
6.2 Vitestによるテスト¶
Vendure v3プロジェクトでは、高速なテストランナーであるVitestが推奨されています。Jestの代わりにVitestを使用することで、設定の簡素化と実行速度の向上が見込めます。
テストの種類¶
-
単体テスト (
.spec.ts): サービスやヘルパー関数のロジックを検証します。DB接続は行わず、依存関係はモック化します。 -
E2Eテスト (
.e2e-spec.ts): 実際のデータベースを使用して、APIエンドポイント(GraphQL)からデータベースまでのフロー全体を検証します。@vendure/testingパッケージのcreateTestEnvironmentを使用します。
Vitest設定例¶
vitest.config.tsを作成し、NestJSのデコレータをサポートするためにunplugin-swcプラグインを使用します。
import { defineConfig } from 'vitest/config';
import swc from 'unplugin-swc';
export default defineConfig({
plugins: [
swc.vite({
jsc: {
transform: {
useDefineForClassFields: false,
legacyDecorator: true,
decoratorMetadata: true,
},
},
}),
],
test: {
globals: true,
environment: 'node',
},
});
テスト実行コマンド¶
# 全テスト実行
pnpm test
# UIモードで実行
pnpm run test:ui
# カバレッジ計測
pnpm run test:coverage
# 関連テストの実行(変更されたファイルのみ)
pnpm run test:related src/my-changed-file.ts
6.3 統合テスト基盤(バックエンド)¶
バックエンド統合テスト環境が apps/vendure-server/test/integration 配下に整備されています。
SQLite(インメモリ)を使用し、実際のVendureサーバーを起動してGraphQL API経由でテストを実行します。
セットアップの特徴¶
- DB: Postgres/PGroonga(docker compose dev)を使用
- Initializer:
InMemorySqljsInitializerにより、テストごとに分離不要な単一DB構成 - Client:
TestGraphQLClientにより、node-fetchベースの安定したGraphQLリクエストが可能(認証トークン自動管理)
実行方法¶
# Vendureサーバーの統合テストのみ実行
pnpm --filter ritsubi-vendure-server test:integration
# ルートから実行(Turbo)
pnpm test:integration
テストの作成方法¶
test/integration/setup/test-environment.ts から createTestEnv をインポートして使用します。
import { createTestEnv, TestServer } from './setup/test-environment';
import { TestGraphQLClient } from './helpers/graphql-client';
import { initialData } from './fixtures/initial-data';
import gql from 'graphql-tag';
describe('My Feature', () => {
let server: TestServer;
let adminClient: TestGraphQLClient;
beforeAll(async () => {
const env = await createTestEnv();
server = env.server;
await server.init({ initialData, logging: false });
// クライアントの初期化(ポート動的取得)
const port = server.app.getHttpServer().address().port;
adminClient = new TestGraphQLClient(`http://localhost:${port}/admin-api`);
await adminClient.asSuperAdmin();
});
afterAll(async () => {
await server.destroy();
});
it('should work', async () => {
const result = await adminClient.query(gql`...`);
expect(result).toBeDefined();
});
});
参照リンク¶
プロジェクト内ドキュメント¶
- Vendure開発ガイド: プロジェクト固有のセットアップと開発ガイド
- Vendureプラグイン実装ガイド: カスタムプラグインの実装詳細
- Vendure Dashboard 日本語化手順書: Dashboardの国際化設定
公式ドキュメント¶
- Vendure公式ドキュメント: Vendureの公式ドキュメント(英語)
- Vendure GitHub: ソースコードとイシュー
更新日: 2025-01-XX
メンテナー: Ritsubi開発チーム