Rspec can't get controller create action to work - ruby-on-rails

Been working on writing the correct test all day, but I can't figure it out.
A comment belongs to a user and an outlet
Each outlet has a user
Each out has many comments
Each user has many outlets
At the moment I'm getting this error:
Failure/Error: let(:outlet) { FactoryGirl.build(:outlet) }
ActiveRecord::RecordInvalid:
Validation failed: Username has already been taken, Email has already been taken
I really don't know what else to try. I've tried switching my factories and tests around a bunch, but only got different errors. Any help would be greatly appreciated
Factories:
FactoryGirl.define do
factory :comment do
body "This is a comment"
user
outlet
end
factory :invalid_comment, class: Comment do
body "This is a comment"
user nil
outlet nil
end
end
FactoryGirl.define do
factory :outlet do
category "vent"
title "MyString"
body "MyText"
urgency 1
user
end
factory :invalid_outlet, class: Outlet do
category "qualm"
title ""
body ""
urgency 3
user factory: :user
end
end
FactoryGirl.define do
factory :user do
sequence(:username) { |n| "test_user#{n}" }
sequence(:email) { |n| "test_user#{n}#email.com" }
password "password"
end
factory :invalid_user, class: User do
username ""
email ""
password ""
end
end
Test
describe 'create' do
context 'with valid attributes' do
let(:outlet) { FactoryGirl.create(:outlet) }
let(:comment_params) { FactoryGirl.attributes_for(:comment) }
let(:create) { post :create, params: { id: outlet , comment: comment_params } }
it "creates new comment" do
puts outlet
puts comment_params
expect { create }.to change { Comment.count }.by 1
end
end
end
class CommentsController < ApplicationController
def new
#comment = Comment.new
end
def create
#outlet = Outlet.find(params[:id])
#comment = #outlet.comments.build(comment_params)
if #comment.save
redirect_to(#outlet)
end
end
private
def comment_params
params.require(:comment).permit(:body, :outlet_id, :user_id)
end
end

The error suggests that your generated username/emails are colliding with names in the database. That might happen if you have username created from one run still in your database on a later run.
If you are are not already using it, consider adding the DatabaseCleaner gem to your project. It provides various ways to ensure your database is reset between runs.

Related

How can I create class “Item” before the class “Transaction” exist, with FactoryBot?

I was wondering if anyone can help. I am receiving an error message in the terminal saying Validation failed: Item must exist. I think that happens because FactoryBot is trying to create a Transaction class before the Item class, I think, I have to find a way to make the Item exist first before the Transaction, I tried many different ways, but had no success so far. optional: true is not an option, appreciate any help.
ps: this is my first time I am asking a help in StackOverflow so apologies if something is not right in my post.
Down below you can find 3 files, if you want to see other files let me know.
require "test_helper"
class PurchasesControllerTest < ActionDispatch::IntegrationTest
describe "#index" do
let(:user_id) { Faker::Internet.uuid }
let(:payload) { { user_id:, account_number: 1 } }
let(:purchase_count) { 3 }
before do
WebMock.stub_request(:get, "#{ENV['IDENTITY_SERVICE_URL']}/users/#{user_id}")
.to_return(status: 200, body: user_response(user_id:), headers: jsonapi_headers)
create(:as_asset)
purchase_count.times do
create(:purchase, user_id: 1)
end
end
it "returns successful response" do
get purchases_url, headers: authorization_header(payload)
assert_response :success
assert_match(/"total":#{purchase_count}/, response.body)
end
context "when unauthorized" do
it "returns unauthorized response" do
get purchases_url
assert_response :unauthorized
end
end
end
end
FactoryBot.define do
factory :purchase do
user_id { Faker::Internet.uuid }
for_as_asset
trait :for_gasset do
association :item, factory: :gasset
end
trait :for_as_asset do
association :item, factory: :as_asset
end
after(:create) do |object|
create(:transaction, purchase_id: object.id, purchase_type: "Purchase")
end
end
end
FactoryBot.define do
factory :transaction do
item_id { 4 }
item_type { "AsAsset" }
user_id { Faker::Internet.uuid }
purchase_ref { "Purchase reference" }
end
end
The answer to my question:
Added item_id: object.item_id, item_type: object.item_type inside the block statement of purchase factory.
FactoryBot.define do
factory :purchase do
for_gasset
after(:create) do |object|
create(:transaction, user_id: object.user_id, item_id: object.item_id, item_type: object.item_type, purchase_id: object.id, purchase_type: "Purchase")
end
trait :for_gasset do
association :item, factory: :gasset
end
trait :for_as_asset do
association :item, factory: :as_asset
end
user_id { Faker::Internet.uuid }
end
end

How to test invalid_attribute using rspec and factorygirl

I am learning how to test on rails from this tutorial.
On one part of the tutorial, it shows how to write invalid_attribute test:
require 'rails_helper'
RSpec.describe ContactsController, type: :controller do
describe "POST #create" do
context "with valid attributes" do
it "create new contact" do
post :create, contact: attributes_for(:contact)
expect(Contact.count).to eq(1)
end
end
context "with invalid attributes" do
it "does not create new contact" do
post :create, contact: attributes_for(:invalid_contact)
expect(Contact.count).to eq(0)
end
end
end
end
I don't understand where :contact and :invalid_contact point to.
Does :contact points to Contact class? It seems like it from FactoryGirl's gh. If so, then how can I create :invalid_contact since there is no :invalid_contact class?
I have tried post :create, contact: attributes_for(:contact, :full_name => nil) but it still fails.
spec/factories/contacts.rb:
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
end
end
First test, with valid attributes pass. On model, there is presence validation validates_presence_of :full_name, :email, :phone_number, :address. What do I add in order to pass "with invalid attributes" test?
The factory will use the class with the same name. So your :contact factory will use the Contact class. You can create a new factory for the invalid contact by specifying the class to use.
factory :invalid_contact, class: Contact do
full_name nil
end
It's also possible to use traits to avoid having two different factories.
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
trait :invalid do
full_name nil
end
end
end
Then use it with attributes_for(:contact, :invalid)
The tutorial you link to says:
Following the spec above, write a spec that uses invalid attributes to
create a new contact. This spec should check that the contact is not
created.
So you need to figure out how to test for :invalid_contact using the example for :contact.
You can just add a let in your spec:
Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.
Source: https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/helper-methods/let-and-let
Then your controller spec would look like this:
...
let(:invalid_contact) { create(:contact, name: nil) }
context "with invalid attributes" do
it "does not create new contact" do
post :create, contact: attributes_for(invalid_contact)
expect(Contact.count).to eq(0)
end
end
...
this way #post action params are picked up from invalid_contact
or as #fanta suggested in comments, you can add a trait to your factory. I prefer my method because other people looking at your code will know why invalid_contact should be invalid without looking at the :contacts factory

Why does my test return a nil class error on an attribute it shouldn't?

I am trying to write a test for my InvitationsController#Create.
This is a POST http action.
Basically what should happen is, once the post#create is first executed, the first thing that needs to do is we need to check to see if a User exists in the system for the email passed in via params[:email] on the Post request.
I am having a hard time wrapping my head around how I do this.
I will refactor later, but first I want to get the test functionality working.
This is what I have:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
end
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
end
end
This is the error I get:
1) Users::InvitationsController POST #create when invited user IS an existing user correctly finds User record of invited user
Failure/Error: post :create, { email: #users.first[:email] }
NoMethodError:
undefined method `name' for nil:NilClass
##myapp/gems/devise-3.2.4/app/controllers/devise_controller.rb:22:in 'resource_name'
# #myapp/gems/devise_invitable-1.3.6/lib/devise_invitable/controllers/helpers.rb:18:in 'authenticate_inviter!'
# #myapp/gems/devise_invitable-1.3.6/app/controllers/devise/invitations_controller.rb:67:in 'current_inviter'
# #myapp/gems/devise_invitable-1.3.6/app/controllers/devise/invitations_controller.rb:71:in 'has_invitations_left?'
I am using FactoryGirl and it works perfectly, in the sense that it returns valid data for all the data-types. The issue here is how do I get RSpec to actually test for the functionality I need.
Edit 1
Added my :user factory:
FactoryGirl.define do
factory :user do
association :family_tree
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
password "password123"
password_confirmation "password123"
bio { Faker::Lorem.paragraph }
invitation_relation { Faker::Lorem.word }
# required if the Devise Confirmable module is used
confirmed_at Time.now
gender 1
end
end
It seems you're using Devise which require you to be logged in before going to the next step. On your error, Devise cannot get the same of your inviter because he's not logged.
Your test should be like this:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
#another_user = FactoryGirl.create(:user_for_login)
sign_in #another_user
end
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
end
end
Example for FactoryGirl model for Devise
factory :user_for_login, class: User do |u|
u.email 'admin#myawesomeapp.com'
u.password 'password'
u.password_confirmation 'password'
u.name "MyName"
end
Of course, you need to add as much data as your validators want.. Basically for Devise you need email, password and password_confirmation. In you case, it seems you also need name.

Rails 4: Factory Girl & Rspec with associated Model

I previously fixed an issue with some code that works though it is a little ugly. Problem now is that it breaks my tests! The idea here is that I can create a Campaign and associate 1 zip-file and one-to-many pdfs.
Previous question and solution:
Rails 4.2: Unknown Attribute or Server Error in Log
Here is the failure message:
console
1) CampaignsController POST #create with valid params
Failure/Error: post :create, campaign: attributes_for(:campaign)
ActiveRecord::RecordNotFound:
Couldn't find Uploadzip with 'id'=
# ./app/controllers/campaigns_controller.rb:15:in `create'
# ./spec/controllers/campaigns_controller_spec.rb:36:in `block (4 levels) in <top (required)>'
..and the rest of the code.
spec/factories/campaigns.rb
FactoryGirl.define do
factory :campaign do |x|
x.sequence(:name) { |y| "Rockfest 201#{y} Orange County" }
x.sequence(:comment) { |y| "Total attendance is #{y}" }
end
end
spec/controllers/campaigns_controller.rb
describe "POST #create" do
context "with valid params" do
before(:each) do
post :create, campaign: attributes_for(:campaign)
end
.........
end
app/controllers/campaigns_controller.rb
class CampaignsController < ApplicationController
......................
def create
#campaign = Campaign.new(campaign_params)
if #campaign.save
zip = Uploadzip.find(params[:uploadzip_id])
zip.campaign = #campaign
zip.save
flash[:success] = "Campaign Successfully Launched!"
redirect_to #campaign
else
................
end
end
.......................
private
def campaign_params
params.require(:campaign).permit(:name, :comment, :campaign_id, uploadpdf_ids: [])
end
end
This appears simple and I assume it is, yet I've tried quit a few things and can't seem to get it to pass. How would I support the new controller logic in this test? Any help is appreciated.
UPDATE
With zetitic's advice, I created the following code in which successfully passes.
before(:each) do
#uploadzip = create(:uploadzip)
post :create, campaign: attributes_for(:campaign), uploadzip_id: #uploadzip
end
Add the uploadedzip_id to the posted params:
before(:each) do
post :create, campaign: attributes_for(:campaign), uploadedzip_id: 123456
end

Reassign user with nil id

I ran into a surprising Rails/Rspec/FactoryGirl (?) quirk today. I've since found a solution, but I'm interested in why the problem happened in the first place.
My example:
let(:old_user) { build(:user) }
let(:new_user) { create(:user) }
let(:post) { build(:post, user: old_user) }
it 'sets the new user' do
post.user_id = new_user.id
post.save
post.reload
post.user.should == new_user
end
This test fails. The user is not assigned properly, at the end of the test post.user is still old_user. (It also is a completely useless test.)
There are a couple of ways to get the test to pass. One is to create the old_user, so that post.user_id is set before trying to reassign it. Another is to replace post.user_id = new_user.id with post.user = User.find(new_user.id). But I'm curious...
What's happening behind the scenes with the original code when the test fails?
Edit: Adding in the factories as requested.
FactoryGirl.define do
factory :user do
# We actually use Faker, but for this purpose I gave strings
name 'Test User'
email 'example#example.com'
password 'changeme'
password_confirmation 'changeme'
end
end
# The Post class has `belongs_to :user`
FactoryGirl.define do
factory :post do
body 'I am a post'
end
end

Resources