https://github.com/turingschool-examples/mini_shop
Are you sick of writing fake data for tests? If you are, then your test files probably look something like this. This is what your index spec probably looks like for a project like MiniShop. You create 3 merchants and 3 items for each merchant and make sure those items show up on your index page...
#index_spec.rb
require "rails_helper"
describe "items index page" do
before :each do
merchant_1 = Merchant.create(
name: "Bentgate Mountaineering",
address: "1313 Washington Ave",
city: "Golden",
state: "CO",
zip: "80401"
)
merchant_2 = Merchant.create(
name: "Wilderness Exchange",
address: "2401 15th St #100",
city: "Denver",
state: "CO",
zip: "80202"
)
merchant_3 = Merchant.create(
name: "Neptune Mountaineering",
address: "633 S Broadway",
city: "Boulder",
state: "CO",
zip: "80305"
)
@item_1 = merchant_1.items.create(
name: "carabiner",
price: 6_00,
description: "use it to clip things",
image: "https://www.rei.com/media/e2c5c7f6-380b-4da1-b4c9-01aafff0ffcd?size=784x588",
inventory: 50
)
@item_2 = merchant_1.items.create(
name: "crampons",
price: 200_00,
description: "spiky things for your feet",
image: "https://www.rei.com/media/63b2eb09-22ed-43b9-8e3b-b4904c3d017a?size=784x588",
inventory: 5
)
@item_3 = merchant_1.items.create(
name: "skis",
price: 600_00,
description: "slide down mountains on there",
image: "https://www.rei.com/media/7886013c-ba41-43f9-a5d1-906f82cc5caf?size=784x588",
inventory: 3
)
@item_4 = merchant_2.items.create(
name: "camalot",
price: 70_00,
description: "rock protection",
image: "https://www.rei.com/media/9c3674f3-1612-464d-b61b-24e5a4bf47b2?size=784x588",
inventory: 20
)
@item_5 = merchant_2.items.create(
name: "tent",
price: 200_00,
description: "for sleeping outside",
image: "https://www.rei.com/media/3e73042d-aded-4741-9b26-a8da7395b69e?size=784x588",
inventory: 20
)
@item_6 = merchant_2.items.create(
name: "nalgene",
price: 10_00,
description: "drink out of this",
image: "https://www.rei.com/media/3c5a2a95-e91e-42c4-82f9-c36b1a4ac51c?size=784x588",
inventory: 40
)
@item_7 = merchant_3.items.create(
name: "ice tools",
price: 250_00,
description: "hack away at ice",
image: "https://www.rei.com/media/10b713fd-f74f-4cc0-ab2f-06f8c91ed467?size=784x588",
inventory: 16
)
@item_8 = merchant_3.items.create(
name: "rope",
price: 250_00,
description: "keep yourself on the wall",
image: "https://www.rei.com/media/b1c10cfd-4bf7-477e-a7e3-ed7335e1fa2e?size=784x588",
inventory: 15
)
@item_9 = merchant_3.items.create(
name: "snowboard",
price: 500_00,
description: "can also slide down on these",
image: "https://www.rei.com/media/153dbcc9-f374-4665-8af5-6a4d84da9983?size=784x588",
inventory: 5
)
visit "/items"
end
it "shows all the items on the page" do
expect(page).to have_content @item_1.name
expect(page).to have_content @item_1.price
expect(page).to have_content @item_1.description
expect(page).to have_css "img[src = '#{@item_1.image}']"
expect(page).to have_content @item_1.inventory
expect(page).to have_content @item_2.name
expect(page).to have_content @item_2.price
expect(page).to have_content @item_2.description
expect(page).to have_css "img[src = '#{@item_2.image}']"
expect(page).to have_content @item_2.inventory
expect(page).to have_content @item_3.name
expect(page).to have_content @item_3.price
expect(page).to have_content @item_3.description
expect(page).to have_css "img[src = '#{@item_3.image}']"
expect(page).to have_content @item_3.inventory
expect(page).to have_content @item_4.name
expect(page).to have_content @item_4.price
expect(page).to have_content @item_4.description
expect(page).to have_css "img[src = '#{@item_4.image}']"
expect(page).to have_content @item_4.inventory
expect(page).to have_content @item_5.name
expect(page).to have_content @item_5.price
expect(page).to have_content @item_5.description
expect(page).to have_css "img[src = '#{@item_5.image}']"
expect(page).to have_content @item_5.inventory
expect(page).to have_content @item_6.name
expect(page).to have_content @item_6.price
expect(page).to have_content @item_6.description
expect(page).to have_css "img[src = '#{@item_6.image}']"
expect(page).to have_content @item_6.inventory
expect(page).to have_content @item_7.name
expect(page).to have_content @item_7.price
expect(page).to have_content @item_7.description
expect(page).to have_css "img[src = '#{@item_7.image}']"
expect(page).to have_content @item_7.inventory
expect(page).to have_content @item_8.name
expect(page).to have_content @item_8.price
expect(page).to have_content @item_8.description
expect(page).to have_css "img[src = '#{@item_8.image}']"
expect(page).to have_content @item_8.inventory
expect(page).to have_content @item_9.name
expect(page).to have_content @item_9.price
expect(page).to have_content @item_9.description
expect(page).to have_css "img[src = '#{@item_9.image}']"
expect(page).to have_content @item_9.inventory
end
end
That's a lot of fake stuff... Surely, there's a better way? With FactoryBot and Faker, you'll never have to write your own dummy data again!
- Add
gem 'factory_bot_rails'
andgem 'faker'
to yourGemfile
- Don't forget to run
bundle install
- Create
/spec/support/factory_bot.rb
#factory_bot.rb
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
- Add
require 'support/factory_bot.rb'
torails_helper.rb
. This file allows us to use shorthand syntax for FactoryBot, meaning we can just callcreate()
instead ofFactoryBot.create()
.
Create /spec/factories/merchants.rb
. The first 3 factories here will allow you to create static instances of the Merchant
class. The last one allows you to create a Merchant
with completely random attributes using Faker. Check out the Faker Github for more info. Some noteworthy ones: FunnyName, Hipster, Superhero, Lebowski, Star Wars (I'm gonna be generating a lot of wookie sentences myself), the list goes on! There's a lot of stuff you can fake!
#merchants.rb
FactoryBot.define do
factory :merchant_1, class: Merchant do
name {"Bent Gate Mountaineering"}
address {"1313 Washington Ave"}
city {"Golden"}
state {"CO"}
zip {"80401"}
end
factory :merchant_2, class: Merchant do
name {"Wilderness Exchange"}
address {"2401 15th St #100"}
city {"Denver"}
state {"CO"}
zip {"80202"}
end
factory :merchant_3, class: Merchant do
name {"Neptune Mountaineering"}
address {"633 S Broadway"}
city {"Boulder"}
state {"CO"}
zip {"80305"}
end
factory :random_merchant, class: Merchant do
name {Faker::Company.name}
address {Faker::Address.street_address}
city {Faker::Address.city}
state {Faker::Address.state}
zip {Faker::Address.zip}
end
end
Create /spec/factories/items.rb
. Here I've made more use of Faker to create a random item generator for testing. Note the association
line. This allows you to relate one class to another. This allows us to create an instance of item
without having to attach it to a merchant
. The item
factory will generate a merchant
for us as well. We'll see later that we can override this and assign values manually.
#items.rb
FactoryBot.define do
factory :random_item, class: Item do
name {Faker::App.name}
price {Faker::Commerce.price}
description {Faker::Lorem.sentence}
image {Faker::LoremPixel.image}
inventory {Faker::Number.number(digits: 3)}
association :merchant, factory: :random_merchant
end
end
You can now generate dummy data in your specs! Here Faker is hard at work making stuff up for you
[2] pry> create(:random_item)
=> #<Item:0x00007f8d421b8ad8
id: 3,
name: "Quo Lux",
price: 72,
description: "Expedita veniam nihil fugiat.",
image: "https://loremflickr.com/300/300",
inventory: 407,
created_at: Fri, 29 Nov 2019 18:41:20 UTC +00:00,
updated_at: Fri, 29 Nov 2019 18:41:20 UTC +00:00,
merchant_id: 3,
active: true>
[3] pry> create(:random_merchant)
=> #<Merchant:0x00007f8d42392bd8
id: 4,
name: "Glover-Greenholt",
address: "461 Steuber Groves",
city: "South Claudio",
state: "Montana",
zip: "63325-9240",
created_at: Fri, 29 Nov 2019 18:41:26 UTC +00:00,
updated_at: Fri, 29 Nov 2019 18:41:26 UTC +00:00>
[4] pry(#<RSpec::ExampleGroups::Item::Relationships>)>
Here is the items index test file spec/features/items/index_spec.rb
. Notice we can override the item factory's creation of a merchant and assign it our own merchant. We want to do this because without assigning it a merchant, a new merchant will be created each time we create an item. You can override other model attributes similarly.
#index_spec.rb
require "rails_helper"
describe "items index page" do
before :each do
merchant_1 = create(:random_merchant)
merchant_2 = create(:random_merchant)
merchant_3 = create(:random_merchant)
@item_1 = create(:random_item, merchant: merchant_1)
@item_2 = create(:random_item, merchant: merchant_1)
@item_3 = create(:random_item, merchant: merchant_1)
@item_4 = create(:random_item, merchant: merchant_2)
@item_5 = create(:random_item, merchant: merchant_2)
@item_6 = create(:random_item, merchant: merchant_2)
@item_7 = create(:random_item, merchant: merchant_3)
@item_8 = create(:random_item, merchant: merchant_3)
@item_9 = create(:random_item, merchant: merchant_3)
visit "/items"
end
it "shows all the items on the page" do
expect(page).to have_content @item_1.name
expect(page).to have_content @item_1.price
expect(page).to have_content @item_1.description
expect(page).to have_css "img[src = '#{@item_1.image}']"
expect(page).to have_content @item_1.inventory
expect(page).to have_content @item_2.name
expect(page).to have_content @item_2.price
expect(page).to have_content @item_2.description
expect(page).to have_css "img[src = '#{@item_2.image}']"
expect(page).to have_content @item_2.inventory
expect(page).to have_content @item_3.name
expect(page).to have_content @item_3.price
expect(page).to have_content @item_3.description
expect(page).to have_css "img[src = '#{@item_3.image}']"
expect(page).to have_content @item_3.inventory
expect(page).to have_content @item_4.name
expect(page).to have_content @item_4.price
expect(page).to have_content @item_4.description
expect(page).to have_css "img[src = '#{@item_4.image}']"
expect(page).to have_content @item_4.inventory
expect(page).to have_content @item_5.name
expect(page).to have_content @item_5.price
expect(page).to have_content @item_5.description
expect(page).to have_css "img[src = '#{@item_5.image}']"
expect(page).to have_content @item_5.inventory
expect(page).to have_content @item_6.name
expect(page).to have_content @item_6.price
expect(page).to have_content @item_6.description
expect(page).to have_css "img[src = '#{@item_6.image}']"
expect(page).to have_content @item_6.inventory
expect(page).to have_content @item_7.name
expect(page).to have_content @item_7.price
expect(page).to have_content @item_7.description
expect(page).to have_css "img[src = '#{@item_7.image}']"
expect(page).to have_content @item_7.inventory
expect(page).to have_content @item_8.name
expect(page).to have_content @item_8.price
expect(page).to have_content @item_8.description
expect(page).to have_css "img[src = '#{@item_8.image}']"
expect(page).to have_content @item_8.inventory
expect(page).to have_content @item_9.name
expect(page).to have_content @item_9.price
expect(page).to have_content @item_9.description
expect(page).to have_css "img[src = '#{@item_9.image}']"
expect(page).to have_content @item_9.inventory
end
end
We can further refactor the before :each
block with create_list
. create_list
returns an array of model objects.
#index_spec.rb
require "rails_helper"
describe "items index page" do
before :each do
merchants = create_list(:random_merchant, 3)
@merchant_1_items = create_list(:random_item, 3, merchant: merchants[0])
@merchant_2_items = create_list(:random_item, 3, merchant: merchants[1])
@merchant_3_items = create_list(:random_item, 3, merchant: merchants[2])
visit "/items"
end
it "shows all the items on the page" do
expect(page).to have_content @merchant_1_items[0].name
expect(page).to have_content @merchant_1_items[0].price
expect(page).to have_content @merchant_1_items[0].description
expect(page).to have_css "img[src = '#{@merchant_1_items[0].image}']"
expect(page).to have_content @merchant_1_items[0].inventory
expect(page).to have_content @merchant_1_items[1].name
expect(page).to have_content @merchant_1_items[1].price
expect(page).to have_content @merchant_1_items[1].description
expect(page).to have_css "img[src = '#{@merchant_1_items[1].image}']"
expect(page).to have_content @merchant_1_items[1].inventory
expect(page).to have_content @merchant_1_items[2].name
expect(page).to have_content @merchant_1_items[2].price
expect(page).to have_content @merchant_1_items[2].description
expect(page).to have_css "img[src = '#{@merchant_1_items[2].image}']"
expect(page).to have_content @merchant_1_items[2].inventory
expect(page).to have_content @merchant_2_items[0].name
expect(page).to have_content @merchant_2_items[0].price
expect(page).to have_content @merchant_2_items[0].description
expect(page).to have_css "img[src = '#{@merchant_2_items[0].image}']"
expect(page).to have_content @merchant_2_items[0].inventory
expect(page).to have_content @merchant_2_items[1].name
expect(page).to have_content @merchant_2_items[1].price
expect(page).to have_content @merchant_2_items[1].description
expect(page).to have_css "img[src = '#{@merchant_2_items[1].image}']"
expect(page).to have_content @merchant_2_items[1].inventory
expect(page).to have_content @merchant_2_items[2].name
expect(page).to have_content @merchant_2_items[2].price
expect(page).to have_content @merchant_2_items[2].description
expect(page).to have_css "img[src = '#{@merchant_2_items[2].image}']"
expect(page).to have_content @merchant_2_items[2].inventory
expect(page).to have_content @merchant_3_items[0].name
expect(page).to have_content @merchant_3_items[0].price
expect(page).to have_content @merchant_3_items[0].description
expect(page).to have_css "img[src = '#{@merchant_3_items[0].image}']"
expect(page).to have_content @merchant_3_items[0].inventory
expect(page).to have_content @merchant_3_items[1].name
expect(page).to have_content @merchant_3_items[1].price
expect(page).to have_content @merchant_3_items[1].description
expect(page).to have_css "img[src = '#{@merchant_3_items[1].image}']"
expect(page).to have_content @merchant_3_items[1].inventory
expect(page).to have_content @merchant_3_items[2].name
expect(page).to have_content @merchant_3_items[2].price
expect(page).to have_content @merchant_3_items[2].description
expect(page).to have_css "img[src = '#{@merchant_3_items[2].image}']"
expect(page).to have_content @merchant_3_items[2].inventory
end
end
For reference, here's what the merchant items index test /spec/features/items/merchant_items_index_spec
looks like.
#merchant_items_index_spec.rb
require "rails_helper"
describe "items index page" do
before :each do
merchant_1 = create(:random_merchant)
@items = create_list(:random_item, 3, merchant: merchant_1)
visit "/merchants/#{merchant_1.id}/items"
end
it "shows all the items for that merchant" do
within "#item-#{@items[0].id}" do
expect(page).to have_content @items[0].name
expect(page).to have_content @items[0].price
expect(page).to have_content @items[0].description
expect(page).to have_css "img[src = '#{@items[0].image}']"
expect(page).to have_content "Active"
expect(page).to have_content @items[0].inventory
end
within "#item-#{@items[1].id}" do
expect(page).to have_content @items[1].name
expect(page).to have_content @items[1].price
expect(page).to have_content @items[1].description
expect(page).to have_css "img[src = '#{@items[1].image}']"
expect(page).to have_content "Active"
expect(page).to have_content @items[1].inventory
end
within "#item-#{@items[2].id}" do
expect(page).to have_content @items[2].name
expect(page).to have_content @items[2].price
expect(page).to have_content @items[2].description
expect(page).to have_css "img[src = '#{@items[2].image}']"
expect(page).to have_content "Active"
expect(page).to have_content @items[2].inventory
end
end
end
Here's my favorite part. Now you can seed your DB with as much dummy data as you want! You have to use FactoryBot.create()
syntax here since we're operating outside of RSpec. Below, I've created 3 random merchants with 20 random items each. Now seed your database and see it full of fake stuff!
#seeds.rb
merchants = FactoryBot.create_list(:random_merchant, 3)
FactoryBot.create_list(:random_item, 20, merchant: merchants[0])
FactoryBot.create_list(:random_item, 20, merchant: merchants[1])
FactoryBot.create_list(:random_item, 20, merchant: merchants[2])