Skip to content

Instantly share code, notes, and snippets.

@Penitent0
Forked from jfangonilo/factorybot_faker_demo.md
Created September 15, 2022 22:00
Show Gist options
  • Save Penitent0/46dcb1f3f9bf82395b7df171905527b5 to your computer and use it in GitHub Desktop.
Save Penitent0/46dcb1f3f9bf82395b7df171905527b5 to your computer and use it in GitHub Desktop.
FactoryBot/Faker Demo

FactoryBot/Faker Demo

For Turing BE Mod2 - MiniShop

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!

Setup

  1. Add gem 'factory_bot_rails' and gem 'faker' to your Gemfile
  2. Don't forget to run bundle install
  3. Create /spec/support/factory_bot.rb
#factory_bot.rb
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
  1. Add require 'support/factory_bot.rb' to rails_helper.rb. This file allows us to use shorthand syntax for FactoryBot, meaning we can just call create() instead of FactoryBot.create().

Create your test dummies

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

Write tests without dummy data!

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

Seed your Dev DB!

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])

Useful Links

FactoryBot Github

Faker Github

Video Tutorial

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment