mcpField
S
spree-testing

spree-testing

github.com/spreeテスト・品質10224 回インストール4 週間前に更新
リポジトリ

spree-testing について

spree-testingは、Spreeアプリや拡張機能を開発する方向けのテストガイドです。Rails、ダッシュボード、ブラウザテストのツールと、ファクトリー、ワークフロー、APIテストや一般的な失敗例を解説します。

主な機能

  1. 01RSpecとFactory Botの設定
  2. 02Spreeモデルとチェックアウト用ファクトリー
  3. 03ワークフロー・フック・購読者のテスト
  4. 04API v3の共有コンテキストと例
  5. 05ダッシュボードのVitest・Playwrightテスト
  6. 06拡張機能のダミーアプリテスト

活用シーン

  1. 01Spreeアプリへのモデル・ワークフローテスト追加
  2. 02API v3の認証と権限のテスト
  3. 03ブラウザテストによるダッシュボード動作の確認
  4. 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_response
  • ActiveJob::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

コメント