Instead of the Rails standard minitest, we will introduce RSpec, which has a higher adoption rate than minitest in practice. We will learn more about ** TDD (Test Driven Development) and BDD (Behavior Driven Development) **, which are test methods.
Capybara ・ ・ ・ Simulates browser operation of web applications FactoryBot ・ ・ ・ A gem that supports the creation of a database for testing
group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'rspec-rails', '~> 4.0.0'
gem 'factory_bot_rails', '~> 4.11'
gem 'capybara', '~> 2.13'
end
$bundle
$bin/rails g rspec:install
Delete minitest directory
$rm -r ./test
Write the following description in .rspec so that the execution result of test can be seen easily.
--format documentation
coloring
--color
spec/spec_helper.rb
require 'capybara/rspec'
RSpec.configure do |config|
config.before(:each, type: :system) do
driven_by :selenium_chrome_headless
end
Install webdrivers that will easily install Chrome Driver
Added gem'webdrivers'
$ mkdir spec/support
$ touch spec/support/driver_setting.rb
spec/support/driver_setting.rb
RSpec.configure do |config|
config.before(:each, type: :system) do
#When executing Spec, the browser can automatically start up and check the behavior.
# driven_by(:selenium_chrome)
#Browser OFF when Spec is executed
driven_by(:selenium_chrome_headless)
end
end
Normally you can't call a method without a FactoryBot user = FactoryBot.create(:user)
By adding the above settings, the description of FactoryBot can be omitted. user = create(:user)
spec/rails_helper.rb
require "support/factory_bot"
spec/support/factory_bot.rb
RSpec.configure do |config|
#FactoryBot call simplification
config.include FactoryBot::Syntax::Methods
#Prevent factory from not loading properly due to spring
config.before :all do
FactoryBot.reload
end
end
spec/rails_helper.rb
#Uncomment this code
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
** Define a user factory **
spec/factories/users.rb
FactoryBot.define do
factory :user do
name { 'Test user' }
email { '[email protected]' }
password { 'password' }
end
end
** Define a Review factory **
spec/factories/reviews.rb
FactoryBot.define do
factory :review do
title { "MyString" }
author { "MyString" }
description { "MyText" }
user
end
end
This gem speeds up test startup time
group :development do
gem 'spring-commands-rspec'
end
bundle install
and then bundle exec spring binstub rspec
will generate an rspec file in the bin directory
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('rspec-core', 'rspec')
Shoulda Mathers is a gem that allows you to write tests concisely and intuitively by adding one-liner test syntax to RSpec and Minitest.
group :test do
gem 'shoulda-matchers'
#For rails5 or later
gem 'shoulda-matchers', git: 'https://github.com/thoughtbot/shoulda-matchers.git', branch: 'rails-5'
end
spec/support/shoulda_matchers.rb
Shoulda::Matchers.configure do |config|
config.integrate do |with|
with.test_framework :rspec
with.library :rails
end
end
spec/rails_helper.rb
require "support/shoulda_matchers"
config/application.rb
require_relative 'boot'
require 'rails/all'
require 'uri'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module BookReviewApp
class Application < Rails::Application
config.load_defaults 5.1
config.generators.template_engine = :slim
config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
config.generators do |g|
g.test_framework :rspec,
fixtures: false,
view_specs: false,
helper_specs: false,
routing_specs: false
end
end
end
Create a folder called models in the spec folder, create a user_spec.rb file in the models folder, and write the following code
$ mkdir spec/models
$ touch spec/models/user_spec.rb
spec/mogels/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
context "if name is entered" do
it 'if user registration is successful' do
end
end
context "if name is not entered" do
it 'if user registration fails' do
end
end
context 'if email is entered' do
it 'if user registration is successful' do
end
end
context 'if email is not entered' do
it 'if user registration fails' do
end
end
context "if password is entered" do
it 'if user registration is successful' do
end
end
context "if password is not entered" do
it 'if user registration fails' do
end
end
end
#The language of the test description changes depending on the syntax
#example syntax
example 'Japanese' do
#Write code
end
#it syntax
it 'English' do
#Write code
end
If you want to test only this file, you need to specify the file
$ bundle exec rspec spec/models/user_spec.rb
examples: Tests that exist / failures: Tests that failed / pending: Tests that did not run
$ mkdir spec/system/
$ touch spec/system/reviews_spec.rb
spec/system/reviews_spec.rb
require 'rails_helper'
describe 'Review management function', type: :system do
describe 'List display function' do
before do
#Create user A
#Create a review whose author is User A
end
context 'When the user is logged in' do
before do
#User A logs in
end
it 'Reviews created by user A are displayed' do
#Confirm that the title of the created review is displayed on the screen
end
end
end
end
In describe
, describe" what you are trying to describe the specification for "(test target).
The outermost describe describes the subject of the entire Spec file.
Describe a more detailed theme in the deeper description.
context
is used to classify the test contents according to the variation of" status / state ".
You can organize and make the tests easier to see if you need to test whether they work properly under various conditions.
before
is where you write the code to execute the" prerequisites "for the entire area.
If you write before in describe or context, the code written in the before block will be executed before executing the test code in the corresponding describe or context area.
it
describes the expected behavior in sentences and code in the block. If the target works as expected in it, the test is successful.
spec/system/reviews_spec.rb
require 'rails_helper'
describe 'Review management function', type: :system do
describe 'List display function' do
before do
#Create user A
user_a = create(:user, name: 'User A', email: '[email protected]')
#Create a review whose author is User A
create(:review, name: 'First review', user: user_a)
end
context 'When the user is logged in' do
before do
#User A logs in
visit login_path
fill_in 'mail address', with: '[email protected]'
fill_in 'password', with: 'password'
click_button 'log in'
end
it 'Reviews created by user A are displayed' do
#Confirm that the title of the created review is displayed on the screen
expect(page).to have_content 'First review'
end
end
end
end
#If the test fails
$ bundle exec rspec spec/system/reviews_spec.rb
Review management function
List display function
When the user is logged in
Reviews created by user A are displayed(FAILED - 1)
Failures:
1)Review management function List display function Reviews created by user A are displayed when the user is logged in.
Failure/Error: user_a = FactoryBot.create(user, name: 'User A', email: '[email protected]')
NameError:
undefined local variable or method `user' for #<RSpec::ExampleGroups::Nested::Nested::Nested:0x00007fed26346f28>
[Screenshot]: tmp/screenshots/failures_r_spec_example_groups_nested_nested_nested_Reviews created by user a are displayed_412.png
# ./spec/system/reviews_spec.rb:7:in `block (3 levels) in <top (required)>'
Finished in 4.19 seconds (files took 5.32 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/system/reviews_spec.rb:21 #Review management function List display function Reviews created by user A are displayed when the user is logged in.
spec/system/reviews_spec.rb
.
.
.
describe 'New review function' do
let(:login_user) { :user_a }
before do
visit new_review_path
fill_in 'title', with: review_name
click_button 'Post'
end
context 'When you enter a title on the new post screen' do
let(:review_name) { 'Write a test for a new post' }
it 'Posted successfully' do
expect(page).to have_selector 'alert-success', text: 'Write a test for a new post'
end
end
context 'When you did not enter the title on the new post screen' do
let(:review_name) { '' }
it 'Get an error' do
within '#error_explanation' do
expect(page).to have_content 'Please enter a title'
end
end
end
end
end
let is a function that can be used in an image similar to "assigning a value to a variable in test scope in before processing" (it does not actually create a variable)
let (definition name) {definition content}
You can use a matcher called have_selector to specify a specific element in HTML with a selector (CSS selector).
have_selector
By using the method called within, you can narrow the search range to a specific range on the screen by inspecting the contents of the page inside the within block.
within'specify a range' do
Make a rough outline with comments
spec/models/user_spec.rb
# validations
describe "validations" do
#Existence
describe "presence" do
#Name, email address
it "name and email should not ro be empty/falsy"
#Password, password confirmation
context "when password and confirmation is not present" do
it "@user is invalid"
end
end
#Number of characters characters
describe "charesters" do
#Name: up to 20 characters
context "when name is too long"
it "@user is inavlid"
#Password, password confirmation: minimum 6 characters
describe "when password is too short"
it "@user is inavlid"
end
#Email format
describe "email format" do
#invalid format
context "when invalid format" do
it "@user is inavlid"
#valid format
context "when valid format" do
it "@user is valid"
end
end
end
end
Fill in the comments
spec/models/user_spec.rb
RSpec.describe User, type: :model do
#Pending template
pending "add some examples to (or delete) #{__FILE__}"
# validations
describe "validations" do
#Existence
describe "presence" do
#Name, email address
it { should validate_presence_of :name }
it { should validate_presence_of :email }
#Password, password confirmation
context "when password and confirmation is not present" do
before { user.password = user.password_confirmation = " " }
it { should_not be_valid }
end
end
#Number of characters characters
describe "charesters" do
#Name: up to 20 characters,Password: Minimum 6 characters
it { should validate_length_of(:name).is_at_most(20) }
it { should validate_length_of(:password).is_at_least(6) }
end
#Email format
describe "email format" do
#invalid format
context "when invalid format" do
#Invalid object
it "should be invalid" do
invalid_addr = %w[user@foo,com user_at_foo.org example.user@foo. foo@bar_baz.com foo@bar+baz.com]
invalid_addr.each do |addr|
user.email = addr
expect(user).not_to be_valid
end
end
end
end
#valid format
context "when valid format" do
#Valid objects
it "should be valid" do
valid_addr = %w[[email protected] [email protected] [email protected] [email protected]]
valid_addr.each do |addr|
user.email = addr
expect(user).to be_valid
end
end
end
end
end
↓ Test result ↓
console.log
$ bundle exec rspec spec/models/user_spec.rb
User
add some examples to (or delete) /Users/kiyokawakouji/Rails-app/book_review_app/spec/models/user_spec.rb (PENDING: Not yet implemented)
validations
presence
is expected to validate that :name cannot be empty/falsy
is expected to validate that :email cannot be empty/falsy
when password and confirmation is not present
example at ./spec/models/user_spec.rb:17 (FAILED - 1)
charesters
is expected to validate that the length of :name is at most 20
is expected to validate that the length of :password is at least 6
email format
when invalid format
should be invalid (FAILED - 2)
when valid format
should be valid (FAILED - 3)
Pending: (Failures listed here are expected and do not affect your suite's status)
1) User add some examples to (or delete) /Users/kiyokawakouji/Rails-app/book_review_app/spec/models/user_spec.rb
# Not yet implemented
# ./spec/models/user_spec.rb:5
Failures:
1) User validations presence when password and confirmation is not present
Failure/Error: before { user.password = user.password_confirmation = " " }
NameError:
undefined local variable or method `user' for #<RSpec::ExampleGroups::User::Validations::Presence::WhenPasswordAndConfirmationIsNotPresent:0x00007fd1586e2770>
# ./spec/models/user_spec.rb:16:in `block (5 levels) in <top (required)>'
2) User validations email format when invalid format should be invalid
Failure/Error: user.email = addr
NameError:
undefined local variable or method `user' for #<RSpec::ExampleGroups::User::Validations::EmailFormat::WhenInvalidFormat:0x00007fd15831d6f8>
# ./spec/models/user_spec.rb:34:in `block (6 levels) in <top (required)>'
# ./spec/models/user_spec.rb:33:in `each'
# ./spec/models/user_spec.rb:33:in `block (5 levels) in <top (required)>'
3) User validations when valid format should be valid
Failure/Error: user.email = addr
NameError:
undefined local variable or method `user' for #<RSpec::ExampleGroups::User::Validations::WhenValidFormat:0x00007fd1582d5790>
# ./spec/models/user_spec.rb:46:in `block (5 levels) in <top (required)>'
# ./spec/models/user_spec.rb:45:in `each'
# ./spec/models/user_spec.rb:45:in `block (4 levels) in <top (required)>'
Finished in 0.61427 seconds (files took 5.94 seconds to load)
8 examples, 3 failures, 1 pending
Failed examples:
rspec ./spec/models/user_spec.rb:17 # User validations presence when password and confirmation is not present
rspec ./spec/models/user_spec.rb:31 # User validations email format when invalid format should be invalid
rspec ./spec/models/user_spec.rb:43 # User validations when valid format should be valid
** Explanation of test results below **
As written in the comment out, panding
is a template that shows how to write RSpec (this time I wake it up without erasing it), so it's through.
(FAILED-number)
indicates that the test code failed. This time, three test codes have failed.
For Failures: ~
, the corresponding code that failed the test and the error content are output.
Failures example: ~
outputs the line of the corresponding code in the file where the test failed and the expected behavior.
Since this is a model test, we will check the validates
app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :rememberable,
:validatable, :timeoutable, :registerable, :confirmable
#Is the value of name empty?
validates :name, presence: true
#Is the value of name 20 characters or less?
validates :name, length: { maximum: 20 }
#Make email addresses lowercase when saving email addresses to the database
before_save { self.email = email.downcase }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
#Does the email match the confirmation input?
validates :email, { presence: true, format: { with: VALID_EMAIL_REGEX } }
validates :email, :encrypted_password, confirmation: true
#Is the password value empty?
validates :password, presence: true
#Is the password value more than 6 characters?
validates :password, length: { minimum: 6 }
#Associate User and Review on the database
has_many :reviews, dependent: :destroy, foreign_key: :review_user_id
end
** Setting to rerun only failed tests (--only-failures option) **
spec_helper.rb
RSpec.configure do |config|
config.example_status_persistence_file_path = "./spec/examples.txt"
end
------------------------------------------------------------------
If you are using git.Add the following settings to gitignore as well.
spec/examples.txt
$ bundle exec rspec --only-failures
Rspec option list display
$ rspec --help
Output without running a list of tests
$ rspec --dry-run
Output test warning
$ rspec --warnings
This command displays a warning in the spec file. If there is a spelling mistake, put it in the warning and output it
When I tried $ rsoec --warnings
, the following output was output
WARN: Unresolved or ambiguous specs during Gem::Specification.reset:
diff-lcs (>= 1.2.0, < 2.0)
Available/installed versions of this gem:
- 1.4
- 1.3
WARN: Clearing out unresolved specs. Try 'gem cleanup <gem>'
Please report a bug if this causes problems.
~~abridgement~~
It is said that the same gem is installed in multiple versions and it is not possible to decide which one to handle on the system. You need to remove the old version of the gem and narrow it down to one or decide on a specific version.
** Overall test ** ・ System Spec / Feature Spec ・ Integration test (Request Spec) ・ Functional test (Controller Spec) ** Individual parts test ** ・ Model Spec -Routing Spec · View Spec ・ Helper Spec ・ Mailer (Mailer Spec) -Job (job Spec)
You have to make sure that the test code works as intended. This is also known as the "exercising the code under test".
To prove that it is not a false positive, for example, change to
to to_not
and invert the expectation.
spec/models/user_spec.rb
#If there is no name, it is invalid(success)
it "is invalid without a first name" do
user = User.new(first_name: nil)
user.valid?
expect(user.errors[:first_name]).to include("can't be blank")
end
#If there is no name, it is invalid(Failure)
it "is invalid without a first name" do
user = User.new(first_name: nil)
user.valid?
expect(user.errors[:first_name]).to_not include("can't be blank")
end
Rails RSpec Preparation and Test Code Basics [Rails] "RSpec + FactoryBot + Capybara + Webdrivers" introduction & initial setting to test writing Building SystemSpec environment with Rails + Selenium + Docker https://github.com/everydayrails/everydayrails-rspec-2017 Introduction to RSpec that can be used, Part 1 "Understanding the basic syntax and useful functions of RSpec" RSpec Settings Useful options for Rspec How to use Rails callbacks that I finally understood after suffering
Recommended Posts