@spree/docs 0.1.176 → 0.1.178
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-reference/store.yaml +48 -48
- package/dist/developer/cli/quickstart.md +1 -1
- package/dist/developer/contributing/creating-an-extension.md +2 -2
- package/dist/developer/core-concepts/addresses.md +3 -3
- package/dist/developer/core-concepts/architecture.md +4 -4
- package/dist/developer/core-concepts/calculators.md +2 -2
- package/dist/developer/core-concepts/carts.md +207 -0
- package/dist/developer/core-concepts/channels.md +3 -3
- package/dist/developer/core-concepts/customers.md +1 -1
- package/dist/developer/core-concepts/events.md +1 -1
- package/dist/developer/core-concepts/fulfillments.md +278 -0
- package/dist/developer/core-concepts/imports-exports.md +6 -18
- package/dist/developer/core-concepts/inventory.md +3 -3
- package/dist/developer/core-concepts/media.md +1 -1
- package/dist/developer/core-concepts/metafields.md +1 -1
- package/dist/developer/core-concepts/orders.md +92 -339
- package/dist/developer/core-concepts/payments.md +1 -1
- package/dist/developer/core-concepts/promotions.md +68 -195
- package/dist/developer/core-concepts/returns-exchanges-claims.md +173 -0
- package/dist/developer/core-concepts/store-credits-gift-cards.md +16 -18
- package/dist/developer/core-concepts/stores.md +1 -1
- package/dist/developer/core-concepts/taxes-discounts-fees.md +199 -0
- package/dist/developer/core-concepts/taxes.md +3 -3
- package/dist/developer/create-spree-app/quickstart.md +1 -1
- package/dist/developer/customization/api.md +36 -11
- package/dist/developer/customization/configuration.md +207 -37
- package/dist/developer/customization/decorators.md +8 -8
- package/dist/developer/customization/permissions.md +34 -229
- package/dist/developer/customization/quickstart.md +159 -108
- package/dist/developer/customization/validations.md +1 -1
- package/dist/developer/dashboard/overview.md +1 -1
- package/dist/developer/how-to/build-a-b2b-store.md +21 -0
- package/dist/developer/how-to/build-a-marketplace.md +25 -0
- package/dist/developer/how-to/custom-api-authentication.md +1 -1
- package/dist/developer/how-to/custom-delivery-rate-provider.md +242 -0
- package/dist/developer/how-to/custom-document-numbers.md +154 -0
- package/dist/developer/how-to/custom-order-routing.md +1 -1
- package/dist/developer/how-to/custom-payment-method.md +1 -1
- package/dist/developer/how-to/custom-promotion.md +76 -115
- package/dist/developer/how-to/custom-report.md +2 -2
- package/dist/developer/how-to/custom-stock-splitter.md +5 -5
- package/dist/developer/how-to/sell-digital-products.md +20 -0
- package/dist/developer/multi-tenant/quickstart.md +1 -1
- package/dist/developer/providers/dam.md +14 -0
- package/dist/developer/providers/erp.md +31 -0
- package/dist/developer/providers/fulfillment.md +25 -0
- package/dist/developer/{deployment/telemetry.md → providers/observability.md} +1 -1
- package/dist/developer/providers/overview.md +44 -0
- package/dist/developer/providers/pim.md +25 -0
- package/dist/developer/providers/sso.md +20 -0
- package/dist/developer/sdk/admin/extending.md +1 -1
- package/dist/developer/sdk/admin/quickstart.md +1 -1
- package/dist/developer/sdk/admin/resources.md +1 -1
- package/dist/developer/sdk/extending.md +1 -1
- package/dist/developer/tutorial/admin-api.md +13 -0
- package/dist/developer/tutorial/dashboard-plugin.md +14 -0
- package/dist/developer/tutorial/events.md +7 -161
- package/dist/developer/tutorial/introduction.md +15 -35
- package/dist/developer/tutorial/model.md +7 -98
- package/dist/developer/tutorial/store-api.md +13 -0
- package/dist/developer/tutorial/storefront.md +12 -0
- package/dist/developer/tutorial/testing.md +7 -711
- package/dist/developer/upgrades/5.6-to-6.0.md +2 -2
- package/package.json +1 -1
- package/dist/developer/admin/admin.md +0 -214
- package/dist/developer/admin/authentication.md +0 -59
- package/dist/developer/admin/components.md +0 -711
- package/dist/developer/admin/custom-css.md +0 -256
- package/dist/developer/admin/custom-javascript.md +0 -116
- package/dist/developer/admin/extending-ui.md +0 -1839
- package/dist/developer/admin/form-builder.md +0 -444
- package/dist/developer/admin/helper-methods.md +0 -531
- package/dist/developer/admin/navigation.md +0 -805
- package/dist/developer/admin/tables.md +0 -490
- package/dist/developer/advanced/adding_spree_to_rails_app.md +0 -92
- package/dist/developer/core-concepts/adjustments.md +0 -113
- package/dist/developer/core-concepts/reports.md +0 -208
- package/dist/developer/core-concepts/shipments.md +0 -307
- package/dist/developer/core-concepts/users.md +0 -303
- package/dist/developer/customization/authentication.md +0 -100
- package/dist/developer/customization/checkout.md +0 -202
- package/dist/developer/customization/emails.md +0 -18
- package/dist/developer/customization/routes.md +0 -24
- package/dist/developer/multi-vendor/installation.md +0 -61
- package/dist/developer/multi-vendor/quickstart.md +0 -17
- package/dist/developer/tutorial/admin.md +0 -206
- package/dist/developer/tutorial/api.md +0 -606
- package/dist/developer/tutorial/extending-models.md +0 -393
- package/dist/developer/tutorial/sdk.md +0 -170
- package/dist/developer/upgrades/2.0-to-2.1.md +0 -46
- package/dist/developer/upgrades/2.1-to-2.2.md +0 -59
- package/dist/developer/upgrades/2.2-to-2.3.md +0 -44
- package/dist/developer/upgrades/2.3-to-2.4.md +0 -42
- package/dist/developer/upgrades/3.0-to-3.1.md +0 -47
- package/dist/developer/upgrades/3.1-to-3.2.md +0 -34
- package/dist/developer/upgrades/3.2-to-3.3.md +0 -70
- package/dist/developer/upgrades/3.3-to-3.4.md +0 -36
- package/dist/developer/upgrades/3.4-to-3.5.md +0 -44
- package/dist/developer/upgrades/3.5-to-3.6.md +0 -40
- package/dist/developer/upgrades/3.6-to-3.7.md +0 -62
- package/dist/developer/upgrades/3.7-to-4.0.md +0 -152
- package/dist/developer/upgrades/4.0-to-4.1.md +0 -92
- package/dist/developer/upgrades/4.1-to-4.2.md +0 -109
- package/dist/developer/upgrades/4.10-to-5.0.md +0 -131
- package/dist/developer/upgrades/4.2-to-4.3.md +0 -100
- package/dist/developer/upgrades/4.3-to-4.4.md +0 -125
- package/dist/developer/upgrades/4.4-to-4.5.md +0 -94
- package/dist/developer/upgrades/4.5-to-4.6.md +0 -119
- package/dist/developer/upgrades/4.6-to-4.7.md +0 -39
- package/dist/developer/upgrades/4.8-to-4.9.md +0 -24
- package/dist/developer/upgrades/4.9-to-4.10.md +0 -24
- package/dist/developer/upgrades/4.x-to-4.8.md +0 -52
- package/dist/developer/upgrades/5.0-to-5.1.md +0 -28
- package/dist/developer/upgrades/5.1-to-5.2.md +0 -131
- package/dist/developer/upgrades/5.2-to-5.3.md +0 -338
- package/dist/developer/upgrades/5.3-to-5.4.md +0 -277
- package/dist/developer/upgrades/5.4-to-5.5.md +0 -301
- package/dist/developer/upgrades/5.5-to-5.6.md +0 -207
|
@@ -1,717 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Testing
|
|
3
|
-
description:
|
|
3
|
+
description: Test the feature at every layer — model and request specs with RSpec, dashboard plugin tests with Vitest, and an end-to-end check with Playwright.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
Spree uses [RSpec](https://rspec.info), [Factory Bot](https://github.com/thoughtbot/factory_bot_rails), and [Capybara](https://github.com/teamcapybara/capybara) for testing.
|
|
8
|
-
We also provide the `spree_dev_tools` gem that helps you write Spree-specific tests.
|
|
6
|
+
Each layer of the feature gets the test that suits it: fast Ruby specs for the model and both API endpoints, component tests for the plugin, and one end-to-end pass proving the whole path works.
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
## What this chapter covers
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<details>
|
|
17
|
-
<summary>Setting up an app that doesn't have RSpec yet</summary>
|
|
18
|
-
|
|
19
|
-
For an existing Rails app without test setup, install RSpec and the Spree test helpers:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
```bash Spree CLI (Docker)
|
|
23
|
-
spree generate rspec:install
|
|
24
|
-
spree generate spree_dev_tools:install
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
```bash Without Spree CLI
|
|
28
|
-
bin/rails g rspec:install
|
|
29
|
-
bin/rails g spree_dev_tools:install
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
The `spree_dev_tools` generator adds the Spree-specific helpers to `spec/support/`: authorization helpers (`stub_authorization!`), Factory Bot configuration, Capybara setup for feature tests, and more.
|
|
34
|
-
|
|
35
|
-
</details>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### Create the Fixtures Directory and File
|
|
39
|
-
|
|
40
|
-
When writing tests that involve file attachments (like images, PDFs, etc.), you need fixture files that your factories can use. Here's how to set them up.
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
mkdir -p spec/fixtures/files && printf '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82' > spec/fixtures/files/logo.png
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### What the generators already created
|
|
47
|
-
|
|
48
|
-
If you used `spree generate api_resource` in the [API step](api.md), you already have controller specs (`spec/controllers/spree/api/v3/store/brands_controller_spec.rb` and `…/admin/brands_controller_spec.rb`) and a factory (`spec/factories/spree/brand_factory.rb`). The sections below build the same things by hand — compare as you go, and keep whichever you prefer.
|
|
49
|
-
|
|
50
|
-
For the model spec, create `spec/models/spree/brand_spec.rb` — we'll fill it in below.
|
|
51
|
-
|
|
52
|
-
## Writing Factories
|
|
53
|
-
|
|
54
|
-
Factories provide a convenient way to create test data. Create a factory for your Brand model:
|
|
55
|
-
|
|
56
|
-
```ruby spec/factories/spree/brand_factory.rb
|
|
57
|
-
FactoryBot.define do
|
|
58
|
-
factory :brand, class: Spree::Brand do
|
|
59
|
-
sequence(:name) { |n| "Brand #{n}" }
|
|
60
|
-
sequence(:slug) { |n| "brand-#{n}" }
|
|
61
|
-
|
|
62
|
-
trait :with_description do
|
|
63
|
-
description { '<div>A great brand for <strong>quality products</strong></div>' }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
trait :with_logo do
|
|
67
|
-
after(:create) do |brand|
|
|
68
|
-
brand.logo.attach(
|
|
69
|
-
io: File.new(Rails.root.join('spec', 'fixtures', 'files', 'logo.png')),
|
|
70
|
-
filename: 'logo.png'
|
|
71
|
-
)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
trait :with_products do
|
|
76
|
-
transient do
|
|
77
|
-
products_count { 3 }
|
|
78
|
-
store { nil }
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
after(:create) do |brand, evaluator|
|
|
82
|
-
store = evaluator.store || create(:store)
|
|
83
|
-
create_list(:product, evaluator.products_count, brand: brand)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Factory Usage Examples
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
# Basic factory
|
|
94
|
-
brand = create(:brand)
|
|
95
|
-
|
|
96
|
-
# With traits
|
|
97
|
-
brand = create(:brand, :with_description, :with_logo)
|
|
98
|
-
|
|
99
|
-
# With custom attributes
|
|
100
|
-
brand = create(:brand, name: 'Nike')
|
|
101
|
-
|
|
102
|
-
# Build without persisting (faster for unit tests)
|
|
103
|
-
brand = build(:brand)
|
|
104
|
-
|
|
105
|
-
# Create multiple records
|
|
106
|
-
brands = create_list(:brand, 5)
|
|
107
|
-
|
|
108
|
-
# With associated products
|
|
109
|
-
brand = create(:brand, :with_products)
|
|
110
|
-
brand = create(:brand, :with_products, products_count: 5)
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Writing Model Tests
|
|
114
|
-
|
|
115
|
-
Model tests verify your business logic, validations, associations, and scopes.
|
|
116
|
-
|
|
117
|
-
```ruby spec/models/spree/brand_spec.rb
|
|
118
|
-
require 'rails_helper'
|
|
119
|
-
|
|
120
|
-
RSpec.describe Spree::Brand, type: :model do
|
|
121
|
-
describe 'associations' do
|
|
122
|
-
it 'has many products' do
|
|
123
|
-
association = described_class.reflect_on_association(:products)
|
|
124
|
-
expect(association.macro).to eq(:has_many)
|
|
125
|
-
expect(association.class_name).to eq('Spree::Product')
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
describe 'validations' do
|
|
130
|
-
it 'validates presence of name' do
|
|
131
|
-
brand = build(:brand, name: nil)
|
|
132
|
-
expect(brand).not_to be_valid
|
|
133
|
-
expect(brand.errors[:name]).to include("can't be blank")
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
describe 'slug uniqueness' do
|
|
137
|
-
let!(:existing_brand) { create(:brand, slug: 'nike') }
|
|
138
|
-
|
|
139
|
-
it 'validates uniqueness of slug' do
|
|
140
|
-
brand = build(:brand, slug: 'nike')
|
|
141
|
-
expect(brand).not_to be_valid
|
|
142
|
-
expect(brand.errors[:slug]).to include('has already been taken')
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
describe 'FriendlyId' do
|
|
148
|
-
it 'generates slug from name' do
|
|
149
|
-
brand = create(:brand, name: 'Nike Sportswear', slug: nil)
|
|
150
|
-
expect(brand.slug).to eq('nike-sportswear')
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
it 'handles duplicate names by appending UUID' do
|
|
154
|
-
create(:brand, name: 'Nike', slug: 'nike')
|
|
155
|
-
brand = create(:brand, name: 'Nike', slug: nil)
|
|
156
|
-
expect(brand.slug).to match(/nike-[a-f0-9-]+/)
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
describe '#image' do
|
|
161
|
-
let(:brand) { create(:brand, :with_logo) }
|
|
162
|
-
|
|
163
|
-
it 'returns logo as image for Open Graph' do
|
|
164
|
-
expect(brand.image).to eq(brand.logo)
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Testing Decorators
|
|
171
|
-
|
|
172
|
-
When you extend core Spree models with decorators (see [Extending Core Models](extending-models.md)), test the added functionality:
|
|
173
|
-
|
|
174
|
-
```ruby spec/models/spree/product_decorator_spec.rb
|
|
175
|
-
require 'rails_helper'
|
|
176
|
-
|
|
177
|
-
RSpec.describe 'Spree::Product brand association' do
|
|
178
|
-
let(:store) { @default_store }
|
|
179
|
-
let(:brand) { create(:brand) }
|
|
180
|
-
let(:product) { create(:product) }
|
|
181
|
-
|
|
182
|
-
describe 'brand association' do
|
|
183
|
-
it 'can be assigned a brand' do
|
|
184
|
-
product.brand = brand
|
|
185
|
-
product.save!
|
|
186
|
-
|
|
187
|
-
expect(product.reload.brand).to eq(brand)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
it 'is optional' do
|
|
191
|
-
product.brand = nil
|
|
192
|
-
expect(product).to be_valid
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
describe 'brand.products' do
|
|
197
|
-
let!(:product1) { create(:product, brand: brand) }
|
|
198
|
-
let!(:product2) { create(:product, brand: brand) }
|
|
199
|
-
let!(:other_product) { create(:product) }
|
|
200
|
-
|
|
201
|
-
it 'returns products for the brand' do
|
|
202
|
-
expect(brand.products).to contain_exactly(product1, product2)
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
it 'nullifies brand_id when brand is destroyed' do
|
|
206
|
-
brand.destroy
|
|
207
|
-
expect(product1.reload.brand_id).to be_nil
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
## Writing Controller Tests
|
|
214
|
-
|
|
215
|
-
Controller tests verify that your endpoints respond correctly and perform the expected actions.
|
|
216
|
-
|
|
217
|
-
### Store API Controller Tests
|
|
218
|
-
|
|
219
|
-
Test the API endpoints we created in the [API tutorial](api.md). Store API tests use the `'API v3 Store'` shared context which sets up a store, publishable API key, and JWT tokens — it ships in the `spree_api` gem, so require `spree/api/testing_support/v3/base` at the top of the spec.
|
|
220
|
-
|
|
221
|
-
```ruby spec/controllers/spree/api/v3/store/brands_controller_spec.rb
|
|
222
|
-
require 'rails_helper'
|
|
223
|
-
require 'spree/api/testing_support/v3/base'
|
|
224
|
-
|
|
225
|
-
RSpec.describe Spree::Api::V3::Store::BrandsController, type: :controller do
|
|
226
|
-
render_views
|
|
227
|
-
|
|
228
|
-
include_context 'API v3 Store'
|
|
229
|
-
|
|
230
|
-
let!(:brand1) { create(:brand, name: 'Nike') }
|
|
231
|
-
let!(:brand2) { create(:brand, name: 'Adidas') }
|
|
232
|
-
|
|
233
|
-
before do
|
|
234
|
-
request.headers['X-Spree-Api-Key'] = api_key.token
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
describe 'GET #index' do
|
|
238
|
-
it 'returns a list of brands' do
|
|
239
|
-
get :index
|
|
240
|
-
|
|
241
|
-
expect(response).to have_http_status(:ok)
|
|
242
|
-
expect(json_response['data'].size).to eq(2)
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
it 'returns brand attributes' do
|
|
246
|
-
get :index
|
|
247
|
-
|
|
248
|
-
brand_data = json_response['data'].first
|
|
249
|
-
expect(brand_data).to include('id', 'name', 'slug')
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
it 'returns prefixed IDs' do
|
|
253
|
-
get :index
|
|
254
|
-
|
|
255
|
-
ids = json_response['data'].map { |b| b['id'] }
|
|
256
|
-
ids.each { |id| expect(id).to start_with('brand_') }
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
it 'returns pagination metadata' do
|
|
260
|
-
get :index, params: { page: 1, limit: 1 }
|
|
261
|
-
|
|
262
|
-
expect(json_response['data'].size).to eq(1)
|
|
263
|
-
expect(json_response['meta']).to include(
|
|
264
|
-
'page' => 1,
|
|
265
|
-
'limit' => 1,
|
|
266
|
-
'count' => 2,
|
|
267
|
-
'pages' => 2
|
|
268
|
-
)
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
it 'filters by name' do
|
|
272
|
-
get :index, params: { q: { name_cont: 'nik' } }
|
|
273
|
-
|
|
274
|
-
expect(json_response['data'].size).to eq(1)
|
|
275
|
-
expect(json_response['data'].first['name']).to eq('Nike')
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
it 'sorts by name' do
|
|
279
|
-
get :index, params: { sort: 'name' }
|
|
280
|
-
|
|
281
|
-
names = json_response['data'].map { |b| b['name'] }
|
|
282
|
-
expect(names).to eq(%w[Adidas Nike])
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
describe 'GET #show' do
|
|
287
|
-
it 'returns a brand by prefixed ID' do
|
|
288
|
-
get :show, params: { id: brand1.prefixed_id }
|
|
289
|
-
|
|
290
|
-
expect(response).to have_http_status(:ok)
|
|
291
|
-
expect(json_response['id']).to eq(brand1.prefixed_id)
|
|
292
|
-
expect(json_response['name']).to eq('Nike')
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
it 'returns a brand by slug' do
|
|
296
|
-
get :show, params: { id: brand1.slug }
|
|
297
|
-
|
|
298
|
-
expect(response).to have_http_status(:ok)
|
|
299
|
-
expect(json_response['name']).to eq('Nike')
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
it 'returns 404 for non-existent brand' do
|
|
303
|
-
get :show, params: { id: 'brand_nonexistent' }
|
|
304
|
-
|
|
305
|
-
expect(response).to have_http_status(:not_found)
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
describe 'GET #show with logo' do
|
|
310
|
-
let!(:brand_with_logo) { create(:brand, :with_logo) }
|
|
311
|
-
|
|
312
|
-
it 'includes logo_url when logo is attached' do
|
|
313
|
-
get :show, params: { id: brand_with_logo.prefixed_id }
|
|
314
|
-
|
|
315
|
-
expect(json_response['logo_url']).to be_present
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
### Testing the Product Brand Association
|
|
322
|
-
|
|
323
|
-
Test that the custom Product serializer includes brand data:
|
|
324
|
-
|
|
325
|
-
```ruby spec/controllers/spree/api/v3/store/products_brand_spec.rb
|
|
326
|
-
require 'rails_helper'
|
|
327
|
-
require 'spree/api/testing_support/v3/base'
|
|
328
|
-
|
|
329
|
-
RSpec.describe Spree::Api::V3::Store::ProductsController, type: :controller do
|
|
330
|
-
render_views
|
|
331
|
-
|
|
332
|
-
include_context 'API v3 Store'
|
|
333
|
-
|
|
334
|
-
let(:brand) { create(:brand, name: 'Nike') }
|
|
335
|
-
let!(:product) { create(:product, brand: brand, status: 'active') }
|
|
336
|
-
|
|
337
|
-
before do
|
|
338
|
-
request.headers['X-Spree-Api-Key'] = api_key.token
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
describe 'GET #show' do
|
|
342
|
-
it 'includes brand_id' do
|
|
343
|
-
get :show, params: { id: product.prefixed_id }
|
|
344
|
-
|
|
345
|
-
expect(json_response['brand_id']).to eq(brand.prefixed_id)
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
it 'does not include brand object without expand' do
|
|
349
|
-
get :show, params: { id: product.prefixed_id }
|
|
350
|
-
|
|
351
|
-
expect(json_response).not_to have_key('brand')
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
it 'includes brand object with expand=brand' do
|
|
355
|
-
get :show, params: { id: product.prefixed_id, expand: 'brand' }
|
|
356
|
-
|
|
357
|
-
expect(json_response['brand']).to be_present
|
|
358
|
-
expect(json_response['brand']['id']).to eq(brand.prefixed_id)
|
|
359
|
-
expect(json_response['brand']['name']).to eq('Nike')
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
describe 'GET #index' do
|
|
364
|
-
it 'filters products by brand_id' do
|
|
365
|
-
other_product = create(:product, status: 'active')
|
|
366
|
-
|
|
367
|
-
get :index, params: { q: { brand_id_eq: brand.id } }
|
|
368
|
-
|
|
369
|
-
ids = json_response['data'].map { |p| p['id'] }
|
|
370
|
-
expect(ids).to include(product.prefixed_id)
|
|
371
|
-
expect(ids).not_to include(other_product.prefixed_id)
|
|
372
|
-
end
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
### Admin Controller Tests
|
|
378
|
-
|
|
379
|
-
```ruby spec/controllers/spree/admin/brands_controller_spec.rb
|
|
380
|
-
require 'rails_helper'
|
|
381
|
-
|
|
382
|
-
RSpec.describe Spree::Admin::BrandsController, type: :controller do
|
|
383
|
-
stub_authorization!
|
|
384
|
-
render_views
|
|
385
|
-
|
|
386
|
-
describe 'GET #index' do
|
|
387
|
-
let!(:brand1) { create(:brand, name: 'Adidas') }
|
|
388
|
-
let!(:brand2) { create(:brand, name: 'Nike') }
|
|
389
|
-
|
|
390
|
-
it 'returns a successful response' do
|
|
391
|
-
get :index
|
|
392
|
-
expect(response).to be_successful
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
it 'displays all brands' do
|
|
396
|
-
get :index
|
|
397
|
-
expect(response.body).to include('Adidas')
|
|
398
|
-
expect(response.body).to include('Nike')
|
|
399
|
-
end
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
describe 'GET #new' do
|
|
403
|
-
it 'returns a successful response' do
|
|
404
|
-
get :new
|
|
405
|
-
expect(response).to be_successful
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
it 'displays the new brand form' do
|
|
409
|
-
get :new
|
|
410
|
-
expect(response.body).to include('brand[name]')
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
describe 'POST #create' do
|
|
415
|
-
context 'with valid params' do
|
|
416
|
-
let(:valid_params) do
|
|
417
|
-
{ brand: { name: 'New Brand' } }
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
it 'creates a new brand' do
|
|
421
|
-
expect {
|
|
422
|
-
post :create, params: valid_params
|
|
423
|
-
}.to change(Spree::Brand, :count).by(1)
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
it 'redirects to the edit page' do
|
|
427
|
-
post :create, params: valid_params
|
|
428
|
-
expect(response).to redirect_to(spree.edit_admin_brand_path(Spree::Brand.last))
|
|
429
|
-
end
|
|
430
|
-
end
|
|
431
|
-
|
|
432
|
-
context 'with invalid params' do
|
|
433
|
-
let(:invalid_params) do
|
|
434
|
-
{ brand: { name: '' } }
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
it 'does not create a new brand' do
|
|
438
|
-
expect {
|
|
439
|
-
post :create, params: invalid_params
|
|
440
|
-
}.not_to change(Spree::Brand, :count)
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
it 'returns unprocessable entity status' do
|
|
444
|
-
post :create, params: invalid_params
|
|
445
|
-
expect(response).to have_http_status(:unprocessable_content)
|
|
446
|
-
end
|
|
447
|
-
end
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
describe 'GET #edit' do
|
|
451
|
-
let(:brand) { create(:brand) }
|
|
452
|
-
|
|
453
|
-
it 'returns a successful response' do
|
|
454
|
-
get :edit, params: { id: brand.id }
|
|
455
|
-
expect(response).to be_successful
|
|
456
|
-
end
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
describe 'PUT #update' do
|
|
460
|
-
let(:brand) { create(:brand, name: 'Old Name') }
|
|
461
|
-
|
|
462
|
-
context 'with valid params' do
|
|
463
|
-
it 'updates the brand' do
|
|
464
|
-
put :update, params: { id: brand.id, brand: { name: 'New Name' } }
|
|
465
|
-
expect(brand.reload.name).to eq('New Name')
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
it 'redirects to the edit page' do
|
|
469
|
-
put :update, params: { id: brand.id, brand: { name: 'New Name' } }
|
|
470
|
-
expect(response).to redirect_to(spree.edit_admin_brand_path(brand))
|
|
471
|
-
end
|
|
472
|
-
end
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
describe 'DELETE #destroy' do
|
|
476
|
-
let!(:brand) { create(:brand) }
|
|
477
|
-
|
|
478
|
-
it 'removes the brand from the database' do
|
|
479
|
-
expect {
|
|
480
|
-
delete :destroy, params: { id: brand.id }, format: :html
|
|
481
|
-
}.to change(Spree::Brand, :count).by(-1)
|
|
482
|
-
end
|
|
483
|
-
end
|
|
484
|
-
end
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
## Writing Feature Tests
|
|
488
|
-
|
|
489
|
-
Feature tests (also called system tests) simulate real user interactions using Capybara.
|
|
490
|
-
|
|
491
|
-
### Admin Feature Tests
|
|
492
|
-
|
|
493
|
-
```ruby spec/features/spree/admin/brands_spec.rb
|
|
494
|
-
require 'rails_helper'
|
|
495
|
-
|
|
496
|
-
RSpec.feature 'Admin Brands', type: :feature do
|
|
497
|
-
stub_authorization!
|
|
498
|
-
|
|
499
|
-
describe 'listing brands' do
|
|
500
|
-
let!(:brand1) { create(:brand, name: 'Nike') }
|
|
501
|
-
let!(:brand2) { create(:brand, name: 'Adidas') }
|
|
502
|
-
|
|
503
|
-
it 'displays all brands' do
|
|
504
|
-
visit spree.admin_brands_path
|
|
505
|
-
|
|
506
|
-
expect(page).to have_content('Nike')
|
|
507
|
-
expect(page).to have_content('Adidas')
|
|
508
|
-
end
|
|
509
|
-
end
|
|
510
|
-
|
|
511
|
-
describe 'creating a brand' do
|
|
512
|
-
it 'creates a new brand successfully' do
|
|
513
|
-
visit spree.admin_brands_path
|
|
514
|
-
click_on 'New Brand'
|
|
515
|
-
|
|
516
|
-
fill_in 'Name', with: 'Puma'
|
|
517
|
-
fill_in 'Slug', with: 'puma'
|
|
518
|
-
|
|
519
|
-
click_on 'Create'
|
|
520
|
-
wait_for_turbo
|
|
521
|
-
|
|
522
|
-
expect(page).to have_content('Brand "Puma" has been successfully created!')
|
|
523
|
-
expect(Spree::Brand.find_by(name: 'Puma')).to be_present
|
|
524
|
-
end
|
|
525
|
-
|
|
526
|
-
it 'shows validation errors' do
|
|
527
|
-
visit spree.new_admin_brand_path
|
|
528
|
-
|
|
529
|
-
click_on 'Create'
|
|
530
|
-
wait_for_turbo
|
|
531
|
-
|
|
532
|
-
expect(page).to have_content("can't be blank")
|
|
533
|
-
end
|
|
534
|
-
end
|
|
535
|
-
|
|
536
|
-
describe 'editing a brand' do
|
|
537
|
-
let!(:brand) { create(:brand, name: 'Nike') }
|
|
538
|
-
|
|
539
|
-
it 'updates the brand successfully' do
|
|
540
|
-
visit spree.admin_brands_path
|
|
541
|
-
click_on 'Edit'
|
|
542
|
-
|
|
543
|
-
fill_in 'Name', with: 'Nike Inc.'
|
|
544
|
-
within('#page-header') { click_button 'Update' }
|
|
545
|
-
|
|
546
|
-
wait_for_turbo
|
|
547
|
-
expect(page).to have_content('Brand "Nike Inc." has been successfully updated!')
|
|
548
|
-
expect(brand.reload.name).to eq('Nike Inc.')
|
|
549
|
-
end
|
|
550
|
-
end
|
|
551
|
-
|
|
552
|
-
describe 'deleting a brand' do
|
|
553
|
-
let!(:brand) { create(:brand, name: 'Nike') }
|
|
554
|
-
|
|
555
|
-
it 'removes the brand' do
|
|
556
|
-
expect {
|
|
557
|
-
page.driver.submit :delete, spree.admin_brand_path(brand), {}
|
|
558
|
-
}.to change(Spree::Brand, :count).by(-1)
|
|
559
|
-
end
|
|
560
|
-
end
|
|
561
|
-
end
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
## Test Helpers
|
|
565
|
-
|
|
566
|
-
### Authorization Helper
|
|
567
|
-
|
|
568
|
-
Use `stub_authorization!` to bypass authorization checks in admin tests:
|
|
569
|
-
|
|
570
|
-
```ruby
|
|
571
|
-
RSpec.describe Spree::Admin::BrandsController, type: :controller do
|
|
572
|
-
stub_authorization! # Grants full admin access
|
|
573
|
-
|
|
574
|
-
# ... your tests
|
|
575
|
-
end
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
### wait\_for\_turbo Helper
|
|
579
|
-
|
|
580
|
-
When testing with Turbo/Hotwire, use `wait_for_turbo` to ensure the page has fully loaded:
|
|
581
|
-
|
|
582
|
-
```ruby
|
|
583
|
-
click_on 'Create'
|
|
584
|
-
wait_for_turbo
|
|
585
|
-
expect(page).to have_content('Success!')
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
## Running Tests
|
|
589
|
-
|
|
590
|
-
### Prepare the test database
|
|
591
|
-
|
|
592
|
-
Tests run against a dedicated `spree_test` database, so your development data is never touched. Create it and load the schema once:
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
```bash Spree CLI (Docker)
|
|
596
|
-
spree rails db:test:prepare
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
```bash Without Spree CLI
|
|
600
|
-
bin/rails db:test:prepare
|
|
601
|
-
```
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
You rarely need to run this again — `rails_helper.rb` re-syncs the test schema automatically when you add migrations. It's the command to reach for when the test database gets into a broken or out-of-sync state.
|
|
605
|
-
|
|
606
|
-
### Run the suite
|
|
607
|
-
|
|
608
|
-
`spree rspec` runs `bundle exec rspec` inside the web container with `RAILS_ENV=test`. Everything after `rspec` is passed through, so file paths, line numbers, and flags work as usual:
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
```bash Spree CLI (Docker)
|
|
612
|
-
spree rspec # all tests
|
|
613
|
-
spree rspec spec/models/spree/brand_spec.rb # specific file
|
|
614
|
-
spree rspec spec/models/spree/brand_spec.rb:15 # specific test
|
|
615
|
-
spree rspec --format documentation # documentation format
|
|
616
|
-
spree rspec spec/features/ # only feature tests
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
```bash Without Spree CLI
|
|
620
|
-
bundle exec rspec # all tests
|
|
621
|
-
bundle exec rspec spec/models/spree/brand_spec.rb # specific file
|
|
622
|
-
bundle exec rspec spec/models/spree/brand_spec.rb:15 # specific test
|
|
623
|
-
bundle exec rspec --format documentation # documentation format
|
|
624
|
-
bundle exec rspec spec/features/ # only feature tests
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
<details>
|
|
629
|
-
<summary>Troubleshooting: DatabaseCleaner refuses a remote database URL, or tests hit the development database</summary>
|
|
630
|
-
|
|
631
|
-
Projects scaffolded before July 2026 set `DATABASE_URL` in `docker-compose.dev.yml`. A URL overrides `config/database.yml` for **every** Rails environment, so in-container tests pointed at the development database and tripped DatabaseCleaner's remote-URL safeguard. Two small changes bring an older project up to date:
|
|
632
|
-
|
|
633
|
-
1. In `docker-compose.dev.yml`, replace the `DATABASE_URL` entry under `environment:` with host/username parts, so each Rails environment resolves its own database from `database.yml`:
|
|
634
|
-
|
|
635
|
-
```yaml
|
|
636
|
-
DATABASE_HOST: postgres
|
|
637
|
-
DATABASE_USERNAME: postgres
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
2. In `spec/rails_helper.rb`, force the test environment — the dev container bakes in `RAILS_ENV=development`:
|
|
641
|
-
|
|
642
|
-
```ruby
|
|
643
|
-
ENV['RAILS_ENV'] = 'test'
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
Then restart the stack (`Ctrl+C` and `spree dev`) so the container picks up the new environment.
|
|
647
|
-
|
|
648
|
-
</details>
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
## Best Practices
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
- **Use build over create** — Use `build` instead of `create` when you don't need a persisted record. It's faster because it skips database operations.
|
|
655
|
-
|
|
656
|
-
- **Use let over instance variables** — Prefer `let` and `let!` over instance variables. They're lazily evaluated and scoped to each example.
|
|
657
|
-
|
|
658
|
-
- **One assertion per test** — Keep tests focused on a single behavior. Use `aggregate_failures` if you need multiple assertions.
|
|
659
|
-
|
|
660
|
-
- **Test behavior, not implementation** — Focus on what the code does, not how it does it. This makes tests more resilient to refactoring.
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
### Example: aggregate\_failures
|
|
664
|
-
|
|
665
|
-
```ruby
|
|
666
|
-
it 'creates brand with all attributes', :aggregate_failures do
|
|
667
|
-
brand = create(:brand, name: 'Nike')
|
|
668
|
-
|
|
669
|
-
expect(brand.name).to eq('Nike')
|
|
670
|
-
expect(brand.slug).to eq('nike')
|
|
671
|
-
end
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
## Complete Test Suite Structure
|
|
675
|
-
|
|
676
|
-
After completing this tutorial, your test structure should look like:
|
|
677
|
-
|
|
678
|
-
```
|
|
679
|
-
spec/
|
|
680
|
-
├── factories/
|
|
681
|
-
│ └── spree/
|
|
682
|
-
│ └── brand_factory.rb
|
|
683
|
-
├── models/
|
|
684
|
-
│ └── spree/
|
|
685
|
-
│ ├── brand_spec.rb
|
|
686
|
-
│ └── product_decorator_spec.rb
|
|
687
|
-
├── controllers/
|
|
688
|
-
│ └── spree/
|
|
689
|
-
│ ├── admin/
|
|
690
|
-
│ │ └── brands_controller_spec.rb
|
|
691
|
-
│ └── api/
|
|
692
|
-
│ └── v3/
|
|
693
|
-
│ └── store/
|
|
694
|
-
│ ├── brands_controller_spec.rb
|
|
695
|
-
│ └── products_brand_spec.rb
|
|
696
|
-
├── features/
|
|
697
|
-
│ └── spree/
|
|
698
|
-
│ └── admin/
|
|
699
|
-
│ └── brands_spec.rb
|
|
700
|
-
├── support/
|
|
701
|
-
│ └── (various support files)
|
|
702
|
-
├── rails_helper.rb
|
|
703
|
-
└── spec_helper.rb
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
## Related Documentation
|
|
707
|
-
|
|
708
|
-
* [Model Tutorial](model.md) - Creating the Brand model
|
|
709
|
-
* [Admin Tutorial](admin.md) - Building the admin interface
|
|
710
|
-
* [Extending Core Models](extending-models.md) - Connecting Brands to Products
|
|
711
|
-
* [API Tutorial](api.md) - Creating Brand API endpoints
|
|
712
|
-
* [Spree CLI](../cli/quickstart.md) - `spree rspec` and the other dev workflow commands
|
|
713
|
-
* [RSpec Documentation](https://rspec.info/documentation/) - Official RSpec docs
|
|
714
|
-
* [Factory Bot Documentation](https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md) - Factory Bot guide
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
---
|
|
10
|
+
- Model and factory setup
|
|
11
|
+
- Request specs for the Admin and Store API endpoints
|
|
12
|
+
- Dashboard plugin tests with Vitest
|
|
13
|
+
- An end-to-end Playwright spec: create an FAQ in the dashboard, see it on the storefront
|