I want to create an object and then delete it during an rspec test... is this possible?
This code:
describe User do
it "should be invalid if email is not unique" do
user = FactoryGirl.create(:user, id: 1, email: "g#g.com").should be_valid
FactoryGirl.build(:user, email: "g#g.com").should_not be_valid
User.destroy(user.id)
FactoryGirl.build(:user, email: "g#g.com").should be_valid
end
end
returns the error:
Failure/Error: User.destroy(user.id)
NoMethodError:
undefined method `id' for true:TrueClass
The issue isn't that you can't delete the user within your rspec test - it's that your user object isn't getting the assignment you intended:
FactoryGirl.create(:user, id: 1, email: "g#g.com").should be_valid
evaluates to true, and is assigned to user. Then, when you try to call user.id, it looks for the id property of true and gets stuck.
You might split out that particular line to:
FactoryGirl.build(:user, id: 1, email: "g#g.com").should be_valid
user = FactoryGirl.create(:user, id: 1, email: "g#g.com")
or not worry about local user variable and just delete based on the email address, i.e.
User.destroy((User.find_by email: "g#g.com").id)
Related
I have a user model that expects an email address to be unique however the models spec is failing:
spec/user_spec.rb
it "has a unique email" do
user1 = build(:user, email: "example#email.com")
user2 = build(:user, email: "example#email.com")
expect(user2).to_not be_valid
end
app/model/user.rb
class User < ApplicationRecord
validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, presence: true, uniqueness: true
end
User has a unique email
Failure/Error: expect(user2).to_not be_valid
expected #<User id: nil, email: "example#email.com", created_at: nil, updated_at: nil> not to be valid
The uniqueness validation happens by performing an SQL query into the model's table, searching for an existing record with the same value in that attribute.
So, you just build two users in memory, neither of them is saved to database. Save the user1 to database, and then validate user2.
A possible change to your test may look like this:
it "has a unique email" do
user1 = create(:user, email: "example#email.com")
user2 = build(:user, email: "example#email.com")
expect(user2).to_not be_valid
end
I'm creating a user object using factory bot in an rspec feature spec.
The user belongs_to a company and has the below factory setup
FactoryBot.define do
factory :user do
company
auth_token { Faker::Internet.password }
email { Faker::Internet.email }
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
contact_phone { '+353871234567' }
status { true }
password { Faker::Internet.password }
password_confirmation { password }
end
end
When I create a user via rails console the factory works as expected
user = FactoryBot.create(:user)
The company is created and I can see the company_id as a UUID on the object
#<User id: "bb9fd4c7-bdce-4338-a42d-723876f514bc", company_id: "41e35b15-d766-4b1a-b833-bf1df4241064", first_name: "Frank", last_name: "Grimes", email: "drewshields#gislason.co"...
But when I use the same factory inside rspec feature spec both id and company_id fields are saved as integers not UUIDs
#<User id: 288, company_id: 0, first_name: "Bob", last_name: "Deckow", email: "raleighharber#leannon.info"...
The user creation in rspec is below
let(:user) { create(:user) }
Is there any reason why rspec would have this effect?
Full feature spec below:
# frozen_string_literal: true
# Specs for password reset
require 'rails_helper'
describe 'password reset', type: :feature do
let(:user) { create(:user) }
let(:invaliduser) {
invalid = build(:user, first_name: nil, contact_phone: '123')
invalid.save(validate: false)
invalid
}
context 'active user resets password' do
it 'sends an email to the user' do
visit root_path
first(:link, 'Login').click
click_on 'Forgotten Password?'
fill_in 'Email', with: user.email
click_on 'Reset Password'
expect(page).to have_content('If a matching email was found an email has been sent with password reset instructions.')
expect(ActionMailer::Base.deliveries.last.to).to include(user.email)
expect(ActionMailer::Base.deliveries.last.subject).to include('Please reset your password')
end
end
context 'inactive user resets password' do
it 'sends an email to the {{__}} team' do
visit root_path
first(:link, 'Login').click
click_on 'Forgotten Password?'
fill_in 'Email', with: invaliduser.email
click_on 'Reset Password'
expect(page).to have_content('If a matching email was found an email has been sent with password reset instructions.')
expect(ActionMailer::Base.deliveries.last.to).to include('{{team_email}}')
expect(ActionMailer::Base.deliveries.last.subject).to include('User Account Recovery')
end
end
end
Check that you have the same schema for development and test databases.
Easiest way to just drop and recreate testing DB
RAILS_ENV=test rake db:drop
RAILS_ENV=test rake db:setup
Ensure that you have UUID type ids in schema.rb
I set up Pundit to guard a bunch of request paths, and it's working fine. In particular, if I hit /api/users/:id with a PATCH request, passing the relevant parameters, I get a 403 if I'm not authenticated. Then I wrote this spec
context 'When logged out' do
describe 'user update' do
before(:each) do
#new_user = FactoryBot.create(:user, password: 'testpassword')
end
it 'fails with PATCH' do
patch "/api/users/#{#new_user.id}", params: { given_name: 'Testing Alice' }
expect(response).to have_http_status(:forbidden)
end
end
end
but when I run rspec, I get the following failure:
1) When logged out user update fails with PATCH
Failure/Error: authorize #user
Pundit::NotAuthorizedError:
not allowed to update? this #<User id: 24, email: "nolanschinner#schuster.net", given_name: "Deja", family_name: "Schmeler", role: "USER", password_digest: "$2a$04$3lhKjBj2DfLymYnTfhDZV.IrlhPPxsPHIe.hI0lHdb1...", created_at: "2018-12-07 15:08:00", updated_at: "2018-12-07 15:08:00", verification_token: nil, email_verified: false, gender: nil>
# /Users/morpheu5/.rvm/gems/ruby-2.5.1/gems/pundit-2.0.0/lib/pundit.rb:209:in `authorize'
# ./app/controllers/api/v1/users_controller.rb:55:in `update'
# ...
The method being tested is right here.
As far as I can tell, Pundit raises the exception and this throws rspec into despair. How do I write this test so that it actually works?
The subject is a bit old but, for those still searching for the answer, you should write something like:
context 'When logged out' do
describe 'user update' do
before(:each) do
#new_user = FactoryBot.create(:user, password: 'testpassword')
end
it 'fails with PATCH' do
expect{patch "/api/users/#{#new_user.id}", params: { given_name: 'Testing Alice' }}.to raise_error(Pundit::NotAuthorizedError)
end
end
end
I'm doing exactly what is says in the documentation, but still getting a validation error:
Validation failed: Email has already been taken
FactoryGirl.define do
sequence :email do |n|
"test#{n}#factory.com"
end
factory :user do
email
password '12345678'
password_confirmation '12345678'
goal_id 1
experience_level_id 1
gender 'Female'
end
end
Anyone know what I'm doing wrong here?
EDIT:
Here is the failing spec. It works fine if you uncomment subject block and comment out the FactoryGirl stuff. I'm trying to switch to using FactoryGirl.
require 'spec_helper'
require 'cancan/matchers'
describe User do
# subject(:user) do
# Program.create!(name: 'test', gender: 'Female', goal_id: '1', experience_id: '1')
# User.create!(email: 'test#test.com', password: '12345678', password_confirmation: '12345678', goal_id: '1', experience_level_id: '1', gender: 'Female')
# end
FactoryGirl.create(:program)
puts FactoryGirl.create(:user).inspect
it "should be assigned a program when it's created" do
user.programs.should exist
end
it "should be valid with a name, goal, password, password_confirmation, experience_level, and gender" do
user.should be_valid
end
it { should respond_to(:programs) }
its('programs.last.name') {should == 'Test'}
it "should be assigned imperial as the default measurement_units" do
user.measurement_units.should eq("imperial")
end
it 'validates presence of gender, goal_id, and experience_level_id' do
user = User.new(gender: nil)
user.should validate_presence_of(:gender)
user.should validate_presence_of(:goal_id)
user.should validate_presence_of(:experience_level_id)
end
end
Edit 2:
I've updated my spec following the suggestion of one of the answers, so now my spec runs, but I get a failing test with a validation error. Here is the updated code:
describe User do
subject(:user) do
# Program.create!(name: 'test', gender: 'Female', goal_id: '1', experience_id: '1')
# User.create!(email: 'test#test.com', password: '12345678', password_confirmation: '12345678', goal_id: '1', experience_level_id: '1', gender: 'Female')
FactoryGirl.create(:program)
FactoryGirl.create(:user)
end
it "should be assigned a program when it's created" do
user.programs.should exist
end
And the message from the failing test:
Failures:
1) User should be assigned a program when it's created
Failure/Error: FactoryGirl.create(:user)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
# ./spec/models/user_spec.rb:9:in `block (2 levels) in <top (required)>'
# ./spec/models/user_spec.rb:13:in `block (2 levels) in <top (required)>'
Any ideas?
You can't just create Factory instances in the middle of a test class like that. FactoryGirl is a replacement for the instantiation you had in the code before - leave the subject block, and simply replace the two create lines with the FactoryGirl calls.
(even better, they should be let blocks not a subject block, but that's a different story)
If you are not doing things in a context Rspec knows about, it has no way of cleaning up afterwards.
What do you have in your Gemfile? I have better success using this
gem 'factory_girl_rails', :group => :test
and not listing it in the "group :development, :test do" section.
I'm trying to write a request spec for my user_login API, but can't figure out how to get a valid user that doesn't return a wrong username or password 401 invalid error.
Here is my spec:
require 'spec_helper'
describe User do
subject(:user) do
Program.create!(name: 'test', gender: 'Female', goal_id: '1', experience_id: '1')
User.create!(email: 'test#test.com', password: '12345678', password_confirmation: '12345678', goal_id: '1', experience_level_id: '1', gender: 'Female')
end
it "is logged in" do
post "/api/v1/login", user_login: {email: 'test#test.com', password: '12345678' }
response.status.should be(201)
end
My understanding was that creating a User in the subject line would mean that my test would run against that user. Any idea what I'm doing wrong here?
And Here is the answer
require 'spec_helper'
describe "sessions" do
before do
FactoryGirl.create(:program)
#user = FactoryGirl.create(:user)
end
describe "user" do
it "is logged in" do
post "/api/v1/login", user_login: {email: #user.email, password: #user.password }
response.status.should be(201)
end
end
end
I'm not totally sure what's different than what I was doing in the question. I replaced the subject block with a before block and moved the creation of the program and user into Factories.