Search code examples
ruby-on-railsrubycapybarafactory-botrspec-rails

How to use factories to create test data to be tested with Rspec and Capybara


I'm trying to test my pagination feature with rspec/capybara. I created the articles using FactoryBot but encountered two issues. If I create the data:

  1. in a before block, the data is saved to the database and not wiped after the suite runs.
  2. inside a scenario block and use save_and_open_page to see if the data shows on the screen, it doesn't show. So, I believe that the data doesn't have enough time to save.

gemfile:

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '3.0.3'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem 'rails', '~> 6.1.4', '>= 6.1.4.4'

gem 'react-rails'

# Use postgresql as the database for Active Record
gem 'pg', '~> 1.1'
# Use Puma as the app server
gem 'puma', '~> 5.0'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 5.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.4', require: false

gem "aws-sdk-s3", require: false

group :development, :test do
  gem 'rspec-rails', '~> 5.0.0'
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]

  gem 'factory_bot_rails'

  gem 'lorem_ipsum_amet'

  gem 'dotenv-rails'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 4.1.0'
  # Display performance information such as SQL time and flame graphs for each request in your browser.
  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
  gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
end

group :test do
  gem 'simplecov', require: false
  gem 'codecov', require: false
  gem 'capybara'
  gem 'webdrivers', '~> 5.0'
  gem 'launchy', '~> 2.5'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

This is the factory:

FactoryBot.define do
  factory :article do
    sequence(:title) {|i| "Article #{i}"}
    body {"This is the test body of the article."}
  end
end

This is my test suite. I'm just showing one example because the concept is the same for the rest:

RSpec.feature "Paginations", type: :feature do

  before(:all) do
    ActionController::Base.allow_forgery_protection = true

    # FactoryBot.create_list(:article, 100)
  end

  before(:each) do
    visit articles_path
  end

  after(:all) do
    ActionController::Base.allow_forgery_protection = false
  end

  scenario "user visits articles home page to see only 10 articles", js: true do
    FactoryBot.create_list(:article, 10)

    save_and_open_page

    expect(page).to have_css "div.item-container-sizing.item-container-padding.flex-row-space-between", count: 10
  end
end

Solution

  • Your main issue here is out-of-order execution. You're visiting the page before creating the objects needing for your test, therefore the objects aren't visible on the rendered page (they didn't exist when the page was rendered). For most tests you won't want to be calling visit in a before block, unless you have a series of test that all require the same test data, and visiting the same page. In this case just move the visit into the scenario. Additionally, don't over-specify the selectors for the elements you're looking for because it will make your tests brittle. You don't show what your html looks like, but assuming this page is only showing one list of articles then checking for just one of your classes may be enough, or scoping your expectation to a page area, and then using a more general CSS selector

    scenario "user visits articles home page to see only 10 articles", js: true do
        FactoryBot.create_list(:article, 10)
        visit articles_path
    
        # save_and_open_page
    
        expect(page).to have_css "div.item-container-sizing.item-container-padding.flex-row-space-between", count: 10
      end