RSpec: FactoryBot is seeing duplicate definitions - ruby-on-rails

I'm still new to using FactoryBot so I might be missing something. I am getting this error message:
Could it be due to improper set up in the spec_helper.rb file?
As for defining the user.rb factory, I tried including "associations: contracts" in the user.rb file. I'm still not sure if I should be doing that or is this current format fine for Rspec to pick up the association with contracts.rb?
Any help is appreciated! Thanks!
spec_helper.rb
require 'factory_bot_rails'
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
FactoryBot.definition_file_paths = [File.expand_path('../factories', __FILE__)]
FactoryBot.find_definitions
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
spec/factories/users.rb
FactoryBot.define do
factory :user do
full_name "Test tester"
email "test#tester.com"
password "123456"
end
end
spec/factories/contracts.rb
FactoryBot.define do
factory :contract do
vendor "O2"
starts_on "2019-03-08"
ends_on "2019-03-10"
price 30
end
end
spec/requests/contracts_api_spec.rb
require 'rails_helper'
RSpec.describe "ContractsApi", type: :request do
describe "POST #create" do
before(:each) do
#user = FactoryBot.create(:user)
#current_user = AuthenticateUserCommand.call(#user.email, #user.password)
#contract = #current_user.contracts.create(vendor: "Lebara", starts_on: "2018-12-12", ends_on: "2018-12-14", price: "15")
end
it 'creates a new contract' do
expect { post api_v1_contracts_path, params: #contract }.to change(Contract, :count).by(1)
end
end
end

I believe you don't need to configure FactoryBot in your spec_helper.rb and what you are doing there maybe be causing FactoryBot to load the factories twice.
Try changing the content of spec_helper.rb to just:
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
Also, considering that you are including FactoryBot::Syntax::Methods, in your tests you can simply use #user = create(:user) instead of #user = FactoryBot.create(:user)

Related

Weird behaviour in Rspec

I am running some test in my controller spec and I found a weird behaviour I cannot explain why this is happen.
My Spec look like this:
require 'rails_helper'
describe BooksController do
let(:user_with_books) { create :user, :with_books }
...
describe 'GET /books/:book_id/owners' do
it 'shows all owners of the book' do
book = user_with_books.books.first
user_2 = create :user
book.owners << user_2
get :owners, book_id: user_with_books.books.first.id
expect(assigns(:users).count).to eq 2
expect(assigns(:users).first).to eq user_with_books
expect(assigns(:users).second).to eq user_2
end
end
end
If I run the command
rspec spec/controllers/books_controller_spec.rb:31 everything is green:
But if I run just rspec, the test will fail!
What is Rspec doing on this spec to change the behaviour? What can I do to fix this issue?
EDIT: My spec_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.infer_spec_type_from_file_location!
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
end
According to your spec_helper.rb, you are never calling DatabaseCleaner.clean. It should be called to actually clean the database up in, e. g. before(:each) filter:
config.before(:each) do
DatabaseCleaner.clean
end

Getting factory not registered (ArgumentError) when trying to run test with rspec and factorygirl

I keep getting Factory not registered: user (ArgumentError)
when I try to run my test files with factory_girl_rails. I read several post about that and tried to follow each of them without any success. In my gemfile I have the gem 'factory_girl_rails':
group :development, :test do
gem 'rspec-rails'
gem 'factory_girl_rails'
gem 'annotate'
gem 'better_errors'
gem 'binding_of_caller'
gem 'letter_opener'
gem 'pry-byebug'
gem 'pry-rails'
gem 'quiet_assets'
gem 'spring'
end
I have a spec folder in my project with different folders inside :
controllers, factories, models, lib and support.
In my spec folder I have a spec_helper.rb :
require 'factory_girl_rails'
FactoryGirl.find_definitions
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.include FactoryGirl::Syntax::Methods
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
end
and a rails_helper.rb :
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'devise'
require_relative 'support/controller_macros'
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, :type => :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
end
Inside my factories' folder, I have files for each one of my models for instance tournaments.rb :
require 'factory_girl_rails'
FactoryGirl.define do
factory :tournament do |f|
f.user FactoryGirl.create(:user)
f.accepted [true, false].sample
f.amount { Faker::Number.number(2) }
f.starts_on { Faker::Date.between(2.days.ago, Date.today) }
f.ends_on { Faker::Date.forward(23) }
f.address {Faker::Address.street_address}
f.city { Faker::Address.city }
f.name { Faker::Lorem.sentence }
f.club_organisateur { Faker::Company.name }
f.homologation_number {Faker::Company.swedish_organisation_number}
f.postcode { Faker::Address.postcode }
f.young_fare { Faker::Number.number(2) }
f.iban "jjddjjdjdjdjddj"
f.bic "djdjdjdjdjdjdjdjd"
f.club_email { Faker::Internet.email }
f.region "Ile de France"
end
end
Inside controllers folder, I have for instance tournaments_controller_spec.rb that looks like this :
require 'rails_helper'
require 'factory_girl_rails'
require 'spec_helper'
describe TournamentsController do
render_views
login_user
describe "index" do
it "returns a valid html_body" do
get :index
expect(response.body).to include("Il n'y a pas encore de tournoi référencé sur WeTennis")
end
end
describe "show" do
it "returns a valid tournament si le tournament n'est pas fini" do
get :show
expect(response.body).to include("s'inscrire")
end
end
end
and inside models folder for instance tournament_spec.rb :
require 'rails_helper'
require 'factory_girl_rails'
require 'spec_helper'
describe Tournament do
it "has a valid factory" do
FactoryGirl.create(:tournament).should be_valid
end
it "is invalid without a start date" do
FactoryGirl.build(:tournament, start_date: nil).should_not be_valid
end
it "is invalid without a end date" do
FactoryGirl.build(:tournament, end_date: nil).should_not be_valid
end
it "is invalid without a user" do
FactoryGirl.build(:tournament, user: nil).should_not be_valid
end
end
My users factory looks exactly like the others in folder factories (users.rb)
require 'factory_girl_rails'
FactoryGirl.define do
factory :user do |f|
f.first_name { Faker::Name.first_name}
f.last_name { Faker::Name.last_name }
f.password { Faker::Internet.password}
f.licence_number { Faker::Lorem.sentence }
f.email { Faker::Internet.email }
end
end
same for my user_spec.rb inside my models folder :
require 'rails_helper'
require 'factory_girl_rails'
require 'spec_helper'
describe User do
it "has a valid factory" do
FactoryGirl.create(:user).should be_valid
end
it "is invalid without an email" do
FactoryGirl.build(:user, email: nil).should_not be_valid
end
it "is invalid without a password" do
FactoryGirl.build(:user, password: nil).should_not be_valid
end
end
Why am I getting this error ?
I suspect the problem is your use of the user factory in the tournament factory (and maybe others):
factory :tournament do |f|
f.user FactoryGirl.create(:user)
FactoryGirl allows you to created associated objects quite simply (see the relevant section their documentation). To add an associated object which is created by a factory you can just do:
f.user
This will cause FactoryGirl to create a user object using the equivalent of FactoryGirl.create(:user) when a tournament is created.
Note also that you don't need the f. prefix. It should work find without.
The reason that it is causing an error is that the code FactoryGirl.create(:user) is being executed when your tournament factory loads and the user factory hasn't been loaded at that point. For stuff like: region 'Ile de France it doesn't matter because the string can be evaluated fine but for another factory call it does matter because the factory has to be defined already.

Rails: Can not save on DB during rspec features test

I'm using rspec to perform feature tests and I can't save the user in the DB before the log in.
I'm using factory girl to build the object.
fixture are saved in db at the beginning of the test but are not deleted at the end.(maybe because the test fail. I don't know)
So I can not save the user before clicking on logIn and I get this errror message
--
DEPRECATION WARNING: an empty resource was given to Devise::Strategies::DatabaseAuthenticatable#validate. Please ensure the resource is not nil. (called from set_required_vars at app/controllers/application_controller.rb:43)
spec/features/login_to_mainpage_spec.rb (no error are rescued)
require "rails_helper"
feature 'Navigating to homepage' do
let(:user) { create(:user) }
let(:login_page) { MainLoginPage.new }
scenario "login" do
login_page.visit_page.login(user)
sleep(20)
end
end
A simple page object: spec/features/pages_objects/main_login_page.rb
class MainLoginPage
include Capybara::DSL
def login(user)
fill_in 'email', with: user.email
fill_in 'password', with: "password"
click_on 'logIn'
end
def visit_page
visit '/'
self
end
end
my rails_helper
require 'spec_helper'
require 'capybara/rspec'
require 'capybara/poltergeist'
require "selenium-webdriver"
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/features/page_objects/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = false
config.before :each do
DatabaseCleaner.start
end
config.after :each do
DatabaseCleaner.clean
end
config.infer_spec_type_from_file_location!
config.include Devise::TestHelpers, :type => :controller
end
Capybara.default_driver = :selenium
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, :browser => :firefox)
end
in spec helper:
require 'simplecov'
require 'factory_girl'
require 'rspec/autorun'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
ENV["RAILS_ENV"] ||= 'test'
RSpec.configure do |config|
include ActionDispatch::TestProcess
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.disable_monkey_patching!
if config.files_to_run.one?
config.default_formatter = 'doc'
end
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
end
EDIT 1
I switch "gem 'factory_girl'" to "gem 'factory_girl_rails'"
and add this to application.rb
config.generators do
|g|
g.test_framework :rspec,
:fixtures => true,
:view_specs => false,
:helper_specs => false,
:routing_specs => false,
:controller_specs => true,
:request_specs => true
g.fixture_replacement :factory_girl, :dir => "spec/factories"
end
I still can not save the user in DB.
Everything pass but I put some sleep(10) in by code to refresh my DB and see te records and user is never saved
EDIT 3
My problem is actually very simple. FactoryGirl.create never save the data in DB if I put in my rails_helper:
config.use_transactional_fixtures = true
or
config.before :each do
DatabaseCleaner.start
end
config.after :each do
DatabaseCleaner.clean
end
/spec/factories
FactoryGirl.define do
factory :user do
email 'pierre#tralala.com'
password 'password'
password_confirmation 'password'
end
/spec/support/factory_girl.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
spec/features/login_to_mainpage_spec.rb
let(:user) { create(:user) }
scenario "login" do
login_page.visit_page.login(create(:user))
sleep(5)
end
User will not be saved because of the config I cited before.
I need to have data reseted between tests.
EDIT 4
If I'm using the console
RAILS_ENV=test rails c
FactoryGirl.create(:user) it is saved in db.
I don't understand why it does not work in my tests.
Try using let! to create your user.
From the rSpec documentation:
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method's
invocation before each example.
You said that you are using FactoryGirl but I do not see this in your test. To create my Users with FactoryGirl I always do something like this:
FactoryGirl.create(:user, password: 'test', password_confirmation: 'test', name: 'test')
If setup correctly in your Factory you can just write:
FactoryGirl.create(:user)
To solve your problem with the unique fields FactoryGirl provides you sequences:
sequence :email do |n|
"person#{n}#example.com"
end
factory :user do
email { generate(:email }
end
This will start with "person1#example.com" and add 1 to the number each time FactoryGirl.create(:user) is called.

Undefined method "contain" for controller spec

It's definitely best to divide specs up so you have specs pertaining to each aspect of the MVC architecture, but I think there is a slight crossover with controller specs and view specs.
With view specs, you should only be concerned with the view, but with controller specs I still think it would be a good idea to test that the correct view is rendered, and maybe even test the content of the view, although more in-depth testing of the content should take place in the view spec.
Despite this clear article, https://www.relishapp.com/rspec/rspec-rails/v/2-1/docs/controller-specs/render-views, describing how to do this, I just cannot integrate my view and controller specs.
I keep getting the error undefined method 'contain'!
Here's my spec_helper:
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rspec'
require 'capybara/rails'
require 'factory_girl_rails'
require 'ap'
def set(factory)
#user = FactoryGirl.create(factory)
end
def sign_up(first_name, last_name, profile_name, email, password)
visit "/"
click_link "Register"
fill_in('First name', with: first_name)
fill_in('Last name', with: last_name)
fill_in('Profile name', with: profile_name)
fill_in('Email', with: email)
fill_in('Password', with: password)
fill_in('Password confirmation', with: password)
click_button 'Sign up'
end
def sign_in(email, password)
visit "/"
click_link "Sign In"
fill_in('Email', with: email)
fill_in('Password', with: password)
click_button 'Sign in'
end
def sign_out
visit "/"
click_link "Sign Out"
end
#Webrat.configure do |config|
# config.mode = :rails
#end
#webrat
require 'capybara/poltergeist'
# Capybara.javascript_driver = :poltergeist
Capybara.javascript_driver = :selenium
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
RSpec.configure do |config|
# true means 'yes, filter these specs'
config.filter_run_excluding stress: true
# config.current_driver = :webkit
# config.use_transactional_fixtures = false
# config.include Capybara::DSL
DatabaseCleaner.strategy = :truncation
config.after(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
# config.before(:suite) do
# DatabaseCleaner.strategy = :transaction
# DatabaseCleaner.clean_with(:truncation)
# DatabaseCleaner.start
# end
# config.after(:each) do
# DatabaseCleaner.clean
# end
#config.after(:suite) do
# DatabaseCleaner.strategy = :transaction
# DatabaseCleaner.clean_with(:truncation)
# DatabaseCleaner.clean
# end
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
# config.fixture_path = "#{::Rails.root}/spec/fixtures"
# config.include RSpec::Rails::RequestExampleGroup, type: :feature
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
I18n.enforce_available_locales = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
end
Here's my controller spec:
require "spec_helper"
describe UserFriendshipsController, type: :controller do
render_views
let (:user_1) { FactoryGirl.create(:user_1)}
before {
sign_in user_1
get :index
}
it "renders the :index view" do
response.should render_template(:index)
end
it "view contains expected html" do
# a sanity test more than anything
response.should contain("Welcome to the home page")
end
end
Upon running this spec I get this:
.F
Failures:
1) UserFriendshipsController view contains expected html
Failure/Error: response.should contain("Listing widgets")
NoMethodError:
undefined method `contain' for #<RSpec::Core::ExampleGroup::Nested_1:0x00000008632268>
# ./spec/controllers/user_friendships_spec.rb:18:in `block (2 levels) in <top (required)>'
Finished in 0.1835 seconds
2 examples, 1 failure
Why is this happening? How can I get this to work?
If you look at the relish documentation for the current 2.14 version of Rspec you'll see that they're using match now instead:
expect(response.body).to match /Listing widgets/m
Using the should syntax, this should work:
response.body.should match(/Welcome to the home page/)
Right, it was a very unclear article from Rspec that caused this error. It was using webrat in its example and didn't think to tell you. If anyone else gets here, you can add webrat to your gemfile to use the contain method:
Gemfile
group :test do
gem 'webrat'
end
However, it makes a lot more sense to use rspec's native match method:
expect(response.body).to match /Listing widgets/m

ControllerMacros method not being loaded by RSpec

I've added a ControllerMacro but the method isn't being made available in my specs. My specs will fail with:
NoMethodError: undefined method `attributes_with_foreign_keys' for FactoryGirl:Module
I'm trying to do this following this discussion on Github. I've looked at other similar questions but most point at using config.include ControllerMacros, :type => :controller instead of config.extend ControllerMacros, :type => :controller which I'm doing already.
Now I know that the controller_macros.rb file is being loaded upon starting the test as I've run the specs through RubyMine's debugger but why the method isn't available is beyond me!
spec_helper.rb
require 'simplecov'
SimpleCov.start 'rails'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'email_spec'
require 'rspec/autorun'
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.include(EmailSpec::Helpers)
config.include(EmailSpec::Matchers)
config.include ControllerMacros, :type => :controller
config.include FactoryGirl::Syntax::Methods
config.use_transactional_fixtures = true
config.infer_base_class_for_anonymous_controllers = false
config.order = "random"
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
spec/support/macros/controller_macros.rb
module ControllerMacros
def attributes_with_foreign_keys(*args)
FactoryGirl.build(*args).attributes.delete_if do |k, v|
["id", "type", "created_at", "updated_at"].member?(k)
end
end
end
This example controller spec:
describe "POST create" do
describe "with valid params" do
it "creates a new Course" do
expect {
#post :create, course: FactoryGirl.build(:course)
post :create, course: FactoryGirl.attributes_with_foreign_keys(:course)
}.to change(Course, :count).by(1)
end
The reason of failing:
The macro you defined is a method within a module. You asked Rspec to include the module, so the method is available to Rspec's instance, which should be used similar to describe, it etc.
However, you used this method as a FactoryGirl's class method. Obviously this is not working.
The solution.
It looks you want to have a convenient method to quickly build a set of pre-defined attributes of a model in a special case.
This logic is better not to be a "macro", because it's better to use FactoryGirl as a centralized place to store all logic about models.
So, the most convenient way is, beyond your normal factories, define a special factory for this case, say foo, with all association and other logic you need inside it, and inherit from other normal factory.
If it's name is foo, then you can easily build a hash of foo's attributes like FactoryGirl.attributes_for :foo
Instead of calling:
post :create, course: FactoryGirl.attributes_with_foreign_keys(:course)
Just call:
post :create, course: attributes_with_foreign_keys(:course)
Took advice from #Billy Chan answer.

Resources