spree-testing について
spree-testingは、Spreeアプリや拡張機能を開発する方向けのテストガイドです。Rails、ダッシュボード、ブラウザテストのツールと、ファクトリー、ワークフロー、APIテストや一般的な失敗例を解説します。
主な機能
- 01RSpecとFactory Botの設定
- 02Spreeモデルとチェックアウト用ファクトリー
- 03ワークフロー・フック・購読者のテスト
- 04API v3の共有コンテキストと例
- 05ダッシュボードのVitest・Playwrightテスト
- 06拡張機能のダミーアプリテスト
活用シーン
- 01Spreeアプリへのモデル・ワークフローテスト追加
- 02API v3の認証と権限のテスト
- 03ブラウザテストによるダッシュボード動作の確認
- 04ダミーアプリでの拡張機能テスト実行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ spree/ agent-skills --skill spree-testingインストール後、Claude Code で/spree-testingを入力して読み込まれたか確認します。
詳細
Spree テスト
| 層 | ツール |
|---|---|
Rails アプリ(server/) | RSpec + Factory Bot + spree_dev_tools — Minitest でもフィクスチャでもありません |
ダッシュボード(apps/dashboard) | Vitest(ユニットテスト)+ Playwright(E2E)、事前設定済み |
ストアフロント(apps/storefront) | Next.js アプリに同梱されているもの |
Spree にはサーバー側でレンダリングされる管理画面やストアフロントがないため、Spree の画面を対象とした Capybara の機能スペックを書く必要はありません。ブラウザーのテストは、ダッシュボード/ストアフロントに対して Playwright で行います。
セットアップ
create-spree-app で作成したプロジェクトには、RSpec が設定済みです。従来型のアプリの場合:
# Gemfile
group :development, :test do
gem 'spree_dev_tools'
end
bin/rails g spree_dev_tools:install
ジェネレーターは必要に応じて rspec:install を実行し、spec/support/{spree,factory_bot,database_cleaner,active_job}.rb を書き込みます。spec/support/spree.rb では、以下を読み込みます。
- Spree のファクトリー(
spree/testing_support/factories)と FactoryBot の構文 spree/testing_support/store— 各グループの前に作成され、各例の後にクリーンアップされる、デフォルトの USD ストア(@default_store)Spree::TestingSupport::Preferences(各例の前にreset_spree_preferencesを実行)spree/api/testing_support/v3/base— API v3 の共有コンテキストと JWT ヘルパーtype: :requestとtype: :controller用のjson_responseActiveJob::TestHelper、DatabaseCleaner(トランザクション方式)
spec/factories/spree/**/*_decorator.rb のファクトリーデコレーターは自動的に読み込まれます。
実行
spree rspec # whole suite, in the web container, RAILS_ENV=test
spree rspec spec/models/spree/brand_spec.rb:15 # file / line; any rspec flags pass through
bundle exec rspec … # classic app, from the app root
spree rspec は、何も起動していない状態でも動作します(必要に応じて一時的なコンテナーにフォールバックします)。従来型のアプリでマイグレーションを追加した後は、bin/rails db:test:prepare を実行してください。
使用するファクトリー
判断に迷ったときは、ソースを確認してください:$(bundle show spree_core)/lib/spree/testing_support/factories/。
| 用途 | ファクトリー |
|---|---|
| 顧客/スタッフ | :customer(エイリアスは :user)、:customer_with_addresses、:admin_user(管理者ロールを取得。:without_admin_role トレイトあり) |
| 商品 | :product(デフォルトのバリアントの価格はストア通貨で 19.99)、:product_in_stock、:product_with_option_types、:digital_product;一時属性:price:、compare_at_price:、currency: |
| バリアント | :variant(価格に関する同じ一時属性)、trait :with_no_price、:on_demand_variant |
カート(Spree::Cart) | :cart、:cart_with_line_items(line_items_count:)、:cart_ready_for_delivery(住所と配送提案あり)、:cart_ready_to_complete(支払い額が合計をカバー) |
注文(Spree::Order) | :order(下書き)、:order_with_line_items、:completed_order_with_totals(確定済み)、:completed_order_with_pending_payment、:order_ready_to_ship、:shipped_order |
| フルフィルメント | :fulfillment(エイリアスは :shipment)、:delivery_method(エイリアスは :shipping_method)、:free_delivery_method、:pickup_delivery_method、:digital_delivery_method |
| 在庫 | :stock_location、:stock_level |
| 支払い | :payment、:check_payment_method、:credit_card_payment_method、:store_credit_payment_method |
| カタログ | :category、:collection、:automatic_collection、:custom_field_definition |
| 返品 | :return、:approved_return、:received_return |
| API/権限 | :api_key(:publishable、:secret → スコープ ['write_all']、:revoked)、:role(permissions: %w[read_orders]) |
チェックアウトの状態は、手順をたどるのではなくファクトリーで用意します。OrderWalkthrough.up_to(...) は非推奨です(6.1 で削除)— :cart_ready_for_delivery、:cart_ready_to_complete、または :completed_order_with_totals を使用してください。
カートと注文は異なるモデルです。カートにはステータスがなく、完了すると status(draft/placed/canceled)を持つ Order が作成されます。実際の完了処理をテストするには:
cart = create(:cart_ready_to_complete)
result = Spree.carts_complete_workflow.call(cart: cart)
expect(result).to be_success
order = result.value
expect(order).to be_placed
価格は通貨ごとに設定されます。variant.price はありません。variant.price_in('EUR')&.amount/variant.amount_in('EUR') でアサートし、create(:variant, price: 10, currency: 'EUR') または variant.set_price('EUR', 10) で設定してください。
独自のリソースの場合:spree generate api_resource Brand … を実行すると、spec/factories/spree/brand_factory.rb と Store/Admin コントローラーのスペックが生成されます。
コンテキストの保存と必須の関連付け
- リクエスト外でコードを実行するスペック(ワークフロー、ジョブ、サービス)で、デフォルト以外のストアを使う場合は、
Spree::Current.storeを設定してください。Spree::Current.storeはSpree::Store.defaultにフォールバックしますが、デフォルトストアが存在しない場合はnilとなり、ストアに紐づくレコードは "Store must exist" で失敗します。without_global_store: trueタグの付いたグループでは、デフォルトストアが完全にスキップされます。 - Spree のモデルでは
belongs_toは必須です。モデル用のファクトリーでは、オプションではない関連付けをすべて構築する必要があります。失敗時のメッセージは "<Assoc> must exist" です("can't be blank" ではありません)。空欄が正当な場合は、関連付けにoptional: trueを指定してください。 Spree::CurrentはActiveSupport::CurrentAttributesです。rspec-rails は例ごとにリセットするため、例の中(またはbefore)で設定してください。Spree::Current.storeを割り当てると、その例の残りの間Spree::StoreScopeGuardが有効になります。ストアに紐づくテーブルへのSELECTのうち、ストアスコープも ID による絞り込みもされていないものは記録されます(デフォルトのlogモード)。SPREE_STORE_SCOPE_GUARD=raiseを指定するとSpree::StoreScopeGuard::UnscopedQueryErrorが発生します(Spree 独自の API スイートではこの設定を使用します。CI でも有効にするとよいでしょう)。API v3 のリクエスト/コントローラーのスペックでは、常にガードが有効です。クエリを修正するか(store.products…)、意図的なグローバル検索をSpree::StoreScopeGuard.skip { … }で囲んでください。- 拡張機能のダミーアプリ(
rake test_app)では Active Record の暗号化キーが設定されるため、暗号化されたカラム(Webhook シークレット、ID トークン)は本番環境と同じように動作します。同じ動作をさせるには、ホストアプリのテスト環境にも独自のキーが必要です。
モデルのスペック
require 'rails_helper'
RSpec.describe Spree::Brand, type: :model do
it 'rejects a duplicate slug in the same store' do
create(:brand, slug: 'nike')
expect(build(:brand, slug: 'nike')).not_to be_valid
end
end
永続化が不要な場合は build を優先してください。Rails の存在バリデーション、ストロングパラメーター、単純な関連付けではなく、独自のロジックをテストしてください。
ワークフローとフックのスペック
ワークフローは結果を返します:success?、value、error。
RSpec.describe MyApp::RequirePoNumber do # a 'carts.complete.validate' handler
after { Spree.hooks.unregister('carts.complete.validate', described_class) }
it 'vetoes completion without a PO number' do
Spree.hooks.register('carts.complete.validate', described_class)
cart = create(:cart_ready_to_complete)
result = Spree.carts_complete_workflow.call(cart: cart)
expect(result).not_to be_success
expect(result.error.to_s).to include('PO number')
end
end
登録したものだけを解除してください。Spree.hooks.clear! はコア独自の登録(返品適格性バリデーターなど)も削除し、プロセスの残りの間、それらが使えなくなります。フックキーはワークフロー(hooks :validate, …)から取得します。不明なキーがあると、Eager Loading 時に起動に失敗します。
ハンドラーを単独でユニットテストするには、ダブルまたはハンドラーが読み取る実際のワークフローインスタンス(flow.cart、flow.reject!(msg))を渡してください。
サブスクライバーのスペック
サブスクライバーのジェネレーターはスペックを生成します。パターンは次のとおりです。
RSpec.describe OrderPlacedSubscriber do
let(:order) { create(:completed_order_with_totals) }
let(:event) { Spree::Event.new(name: 'order.placed', payload: { 'id' => order.prefixed_id }) }
it 'submits the order' do
expect(WarehouseClient).to receive_message_chain(:new, :submit).with(order)
described_class.new.handle(event)
end
end
ハンドラーをテストする際は、バスを経由せず handle を直接呼び出してください。何かがイベントを発行することを検証するには、次のようにします。
expect(Spree::Events).to receive(:publish).with('brand.created', anything, anything).and_call_original
brand.save!
publishes_lifecycle_events を持つモデルでは、Spree の共有サンプルを再利用してください。
require 'spree/testing_support/lifecycle_events'
RSpec.describe Spree::Brand do
it_behaves_like 'lifecycle events' # factory: :brand, event_prefix: 'brand' inferred
end
スイート全体でイベントを無効にしている場合(Spree 独自のスイートと同様に Spree::Events.disable! を使う場合)は、例ごとに Spree::Events.enable { … } で再度有効にしてください。
API v3 コントローラーの仕様
共有コンテキスト(spree_dev_tools によってすでに読み込まれている spree/api/testing_support/v3/base から提供):
| コンテキスト | 提供内容 |
|---|---|
'API v3 Store' | store、公開可能な api_key、api_key_headers、user(顧客)、jwt_token、bearer_headers。current_store をスタブします |
'API v3 Store guest' / 'API v3 Store authenticated' | 上記に加えて headers(キーのみ/キーと顧客 JWT) |
'API v3 Admin' | store、secret_api_key(write_all)、admin_user、admin_jwt_token、bearer_headers |
'API v3 Admin authenticated' | 上記に加えて headers |
'API v3 Admin with custom permissions' | let(:custom_permissions) { %w[read_orders] } を設定します。指定したキーのみを持つスタッフ JWT です |
'API v3 Seller' / 'API v3 Seller authenticated' | セラー、セラーユーザー、JWT、X-Spree-Seller-Id ヘッダー |
共有サンプル:'returns 200 OK'、'returns 201 Created'、'returns 204 No Content'、'returns 401 Unauthorized'、'returns 403 Forbidden'、'returns 404 Not Found'、'returns 422 Unprocessable Entity'、'requires API key'、'requires authentication'(いずれも subject を想定します)。
RSpec.describe Spree::Api::V3::Admin::BrandsController, type: :controller do
render_views
routes { Spree::Core::Engine.routes } # API controllers are mounted by the engine
include_context 'API v3 Admin authenticated'
before { request.headers.merge!(headers) }
let!(:brand) { create(:brand) }
describe 'GET #index' do
subject { get :index, params: { q: { name_cont: brand.name[0, 3] } }, as: :json }
it_behaves_like 'returns 200 OK'
it 'returns prefixed ids' do
subject
expect(json_response['data'].map { |b| b['id'] }).to include(brand.prefixed_id)
end
end
context 'with a role lacking write_brands' do
include_context 'API v3 Admin with custom permissions'
let(:custom_permissions) { %w[read_brands] }
subject { post :create, params: { name: 'X' }, as: :json }
it_behaves_like 'returns 403 Forbidden'
end
end
- 常にプレフィックス付き ID(
brand.prefixed_id)を検証し、整数 ID は使わないでください。 - ジェネレーターの仕様はこの形式に従っています。最初から作り直すのではなく、既存のものを拡張してください。
- 独自リソースの権限キー(
read_<resource>/write_<resource>)は、Spree.permissions.register_scopeの後でのみ使用できます。spree-auth-permissionsを参照してください。
ダッシュボードのテスト
cd apps/dashboard
pnpm test # vitest run
pnpm test:e2e # playwright test (testDir ./e2e)
UI と API の間のロジック(withStoreScope によるストアスコープ付きのクエリーキー、フォームからペイロードへのマッパー、権限判定、テーブル状態から Ransack パラメーターへの変換)をユニットテストします。コンポーネントや薄い SDK ラッパーはユニットテストしないでください。機能ごとに Playwright の仕様を 1 つ用意し、レイヤーが連携することを確認します。Admin API でデータを作成し、ログインして、サイドバーから移動し(ルートはストアスコープ付きです)、表示されるテキストを検証します。テストデータには Date.now() を付けてください。E2E の実行ではデータベースを共有します。詳細は spree-dashboard-plugins を参照してください。
拡張機能(gem)のテスト
拡張機能では、生成されたダミーアプリに対してテストします。
bundle exec rake test_app # regenerate spec/dummy after schema changes
bundle exec rspec
test_app/parallel_setup はエンジンと拡張機能にのみ存在し、アプリにはありません。
よくある失敗
| 症状 | 原因/対処法 |
|---|---|
Store must exist/Validation failed: Store must exist | このコンテキストにデフォルトのストアがありません。without_global_store を付けずにタグを設定するか、Spree::Current.store を設定するか、store: を渡してください |
ファクトリーで <Assoc> must exist が発生する | 必須の belongs_to です。関連を構築するか、optional: true に設定してください |
バリアントで NoMethodError: price が発生する | 価格は通貨ごとに設定されています。price_in(currency)/amount_in(currency) を使用してください |
undefined method 'state'/state_machine | ステートマシンはありません。status(be_placed、status: 'canceled')を検証してください |
state: 'complete' を指定した Order ファクトリー | 一時的な指定としては引き続きサポートされていますが、:completed_order_with_totals を優先してください |
| 通貨が違う | Order/カートのファクトリーはデフォルトで USD を使用します。currency: を渡してください |
| 仕様でサブスクライバーが「実行されない」 | 非同期サブスクライバーはエンキューするだけです。handle を直接呼び出すか、perform_enqueued_jobs を使用してください |
OrderWalkthrough の非推奨警告 | カートのファクトリーに切り替えてください |
| 時刻のテストが不安定 | Timecop.freeze/travel_to を使用してください |
詳細情報
- テストチュートリアル:
node_modules/@spree/docs/dist/developer/tutorial/testing.md - ファクトリー:
spree_core/lib/spree/testing_support/factories/ - 共有コンテキスト:
spree_api/lib/spree/api/testing_support/v3/base.rb spree_dev_tools:lib/spree_dev_tools/rspec/support/およびインストールジェネレーター- 関連スキル:
spree-workflows、spree-events-webhooks、spree-resource、spree-auth-permissions、spree-dashboard-plugins