I am doing a controller test but it seems that spec.rb is wrong.
Do you have a suggestion ?
This is my posts_controller.rb:
class PostsController < ApplicationController
def create
#post = Post.new(post_params)
if #post.save
redirect_to #wall
end
end
def destroy
#post.destroy
end
private
def post_params
params.require(:post).permit(:wall, :content)
end
end
and this is my posts_controller_spec.rb:
require 'rails_helper'
describe PostsController do
let(:wall) { create(:wall) }
describe "#create" do
it "saves the new post in the wall" do
post :create, { wall_id: wall, content: "Some text I would like to put in my post" }
end
end
describe "#destroy" do
it "deletes the post in the wall" do
end
end
end
could you please help me to correct my spec.rb?
this is my error:
PostsController
#create
saves the new post in the wall (FAILED - 1)
#destroy
deletes the post in the wall
Failures:
1) PostsController#create saves the new post in the wall
Failure/Error: post :create, post: { wall: wall, content: "Some text I would like to put in my post" }
ActiveRecord::AssociationTypeMismatch:
Wall(#2159949860) expected, got String(#2155957040)
# ./app/controllers/posts_controller.rb:3:in create'
# ./spec/controllers/posts_controller_spec.rb:8:inblock (3 levels) in '
# -e:1:in `'
Finished in 0.9743 seconds (files took 3.94 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/controllers/posts_controller_spec.rb:7 # PostsController#create saves the new post in the wall
Thank you in advance
Your spec doesn't include any expectations, so it's "wrong" in that sense. I suggest you google "RSpec expectations" and/or read the docs (i.e. https://relishapp.com/rspec/rspec-expectations/docs).
As for the error you mentioned in your comment, that reflects a problem with your production code (i.e. the lack of a redirect or render or some create template in the case the #post.save returns nil). Again, googling the error should yield information to help you address this problem or your can read http://guides.rubyonrails.org/layouts_and_rendering.html. If you're new to Rails entirely, I suggest following one of the tutorials, such as https://www.railstutorial.org/
You should also update your question to include that error information, since it's highly relevant and the question is essentially incomplete without it.
You should expect something on your tests. For example, you could do like this:
RSpec.describe PostsController, type: :controller do
let!(:wall) { create(:wall) }
let(:test_post) {
create(:post, wall_id: wall.id, content: "Something") }
}
describe "POST #create" do
let(:post) { assigns(:post) }
let(:test_wall) { create(:wall) }
context "when valid" do
before(:each) do
post :create, params: {
post: attributes_for(:post, wall_id: test_wall.id, content: "Anything")
}
end
it "should save the post" do
expect(post).to be_persisted
end
end
end
end
This way you are expecting a response from rails when you post you parameters. I just coded the post part of the test.
Related
I'm new in testing and learning Rspec, and I can't git it working.
(I have read the book Effective testing with Rspec3, and many tutorials ...also pluralsight.com)
The situation is very simple. In a Companies controller I want to test de Create method, the company model belongs_to user, and is this the code:
I think the problem is when execute
in test: expect(Company).to receive(:new).with(company_params)
or in controller: #company.user=helpers.user
Controller:
class CompaniesController < SessionsController
def create
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
redirect_to companies_path
else
render :edit
end
end
and Rspec:
RSpec.describe CompaniesController, type: :controller do
let(:user) { instance_double(User) }
before do
allow_any_instance_of(ApplicationHelper).to receive(:user){user}
allow(controller).to receive(:authorize){true}
end
describe 'Authenticated user with companies' do
let(:company_params) { {company:{name:"Albert",domain:"www.albert.com"}} }
let(:company) { instance_double(Company) }
before do
allow(Company).to receive(:new){company}
end
describe 'POST #create' do
context "with valid data" do
before { allow(company).to receive(:save){true} }
it "redirects to companies_path" do
expect(Company).to receive(:new).with(company_params)
expect(company).to receive(:user=).with(user)
post :create, params:{company: company_params}
expect(response).to redirect_to(companies_path)
end
My intention is very simple: Use instance_double to mock (or stub) #company, and Company.new, using instance double...to test the create action, and simulate the "save()" returning true...etc
I do not know if I explain myself very well, but given the create action of controlloer , how to test using mocks ans stubs, instance_double?
Thanks
First of all let me explain what we need to test here
def create
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
redirect_to companies_path
else
render :edit
end
end
We are testing create action of a controller. First let us see what this action does? It's just takes comapany_params as input and create a company record in database.
Testing also goes like the same, we need to just pass the input that action required, and need to check whether it's creating record in database or not.
RSpec.describe CompaniesController, type: :controller do
let(:user) { instance_double(User) }
before do
# all your authentication stubing goes here
allow_any_instance_of(ApplicationHelper).to receive(:user){user}
allow(controller).to receive(:authorize){true}
end
describe 'POST#create' do
context 'with valid attributes' do
before do
post :create, { company:{ name:"Albert", domain:"www.albert.com"} }
end
it 'responds with success' do
expect(response.status).to eq(302)
end
it 'creates company' do
company = Company.find_by(name: "Albert")
expect(assigns(:company)).to eq(company)
expect(response).to redirect_to(companies_path())
end
end
context 'with invalid attributes' do
before do
post :create, { company:{ name:"", domain:""} }
end
it 'renders new template' do
expect(response).to render_template(:edit)
end
end
end
end
No need to sub anything here. As per my knowledge, Only when we use any lib classes / background jobs / third party libraries code inside action then we need to stub those code. Because for all those, we will write specs separately. So no need to test again here that's why we'll do stubing.
Thanks to Narsimha Reddy, I have better ideas about how to test.
Eventhough, if I want to stub
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
For testing only de create's response , the solution was in a good use of parameters, and allowing allow(company).to receive(:user=) for the belongs_to association
let(:company_params) {{company:{name:"Albert",domain:"www.albert.com"}}}
let(:ac_company_params) {ActionController::Parameters.new(company_params).require(:company).permit!}
let(:company) { instance_double(Company) }
before do
allow(Company).to receive(:new){company}
allow(company).to receive(:user=)
allow(company).to receive(:save){true}
end
it "redirects to companies_path" do
expect(Company).to receive(:new).with(ac_company_params)
expect(company).to receive(:user=).with(user)
post :create, params: company_params
expect(response).to redirect_to(companies_path)
end
I'm new to RSpec and this error is all new to me. Everything seems routine so I can't seem to debug this issue myself. ERROR: expected result to have changed by 1, but was changed by 0. I'll post my code for clarity.
SUBSCRIBER FACTORY:
FactoryGirl.define do
factory :subscriber do
first_name "Tyler"
last_name "Durden"
email "tyler#example.com"
phone_number "8765555"
end
end
CONTROLLER:
class CommentsController < ApplicationController
def new
#comment = Comment.new
end
def create
#subscriber = Subscriber.order('updated_at desc').first
#comment = #subscriber.comments.build(comments_params)
if #comment.save
flash[:notice] = "Thank you!"
redirect_to subscribers_search_path(:comments)
else
render "new"
end
end
private
def comments_params
params.require(:comment).permit(:fav_drink, :subscriber_id)
end
end
SPEC:
require "rails_helper"
describe SubscribersController do
include Devise::TestHelpers
let(:user) { FactoryGirl.create(:user) }
let(:subscriber) { FactoryGirl.attributes_for(:subscriber) }
it "creates a new comment" do
sign_in(user)
comment = FactoryGirl.attributes_for(:comment)
expect { post :create, subscriber: subscriber, comment: comment }.to change{ Comment.count }.by(1)
end
end
ERROR:
Failure/Error: expect { post :create, subscriber: subscriber, comment: comment }.to change{ Comment.count }.by(1)
expected result to have changed by 1, but was changed by 0
# ./spec/controllers/comment_spec.rb:13:in `block (2 levels) in <top (required)>'
Here, you're showing your comments controller, expecting one of its actions to be hit. However, your test case is actually calling the create route of the Subscriptions controller.
When, in your test case, you write describe SubscribersController do, you are establishing a scope for the HTTP requests you make in that block.
So when you call post :create, subscriber: subscriber, comment: comment,
it's the Subscriptions controller which is being hit.
In general, in order to debug, you should
check that the area of code in question is being called
check that values are correct (here, that would mean that the Comment.create object is successfully saved.
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
I am trying to test that on the creation of a post, the user is redirected to deployments path. I have added in
config.include Rails.application.routes.url_helpers
to extend rails routes to rspec. But my test still fails with the following error
1) Failure/Error: expect(response).to redirect_to(path)
Expected response to be a redirect to <http://test.host/deployments/new> but was a redirect to <http://test.host/deployments/new.1473>.
Expected "http://test.host/deployments/new" to be === "http://test.host/deployments/new.1473".
# -e:1:in `<main>'
Here is the test:
describe "post create" do
before do
allow(model).to receive(:new).and_return(instance)
end
context "where all is not well" do
before do
allow(instance).to receive(:save).and_return(false)
post :create, params_new_instance
end
sets_flash(:error)
it "should render the new form" do
expect(response).to render_template("projects/new")
end
end
context "where all is well" do
before do
allow(instance).to receive(:save).and_return(true)
post :create, params_new_instance
end
sets_flash(:notice)
it "redirects to new_deployments_path" do
expect(controller.controller_path).to eq(new_deployment_path)
end
end
end
project controller
def create
#project=Project.new(params_project)
if #project.save
record_saved
return redirect_to(new_deployment_path(#project))
else
check_for_errors
return render('/projects/new')
end
end
why is this failing? am i approaching this in the right way?
thanks in advance
Are you sure that new_deployments_path instead of new_deployment_path
This is my first suggestion. Them can be more but not enough information: routes.rb and deployments controller for example
I'm using rspec and Factory Girl for testing. When testing the POST #create section of my posts_controller I'm getting the error in the title.
Failures:
1) PostsController POST #create with valid attributes redirects to the post
Failure/Error: response.should redirect_to Post.last
Expected response to be a <redirect>, but was <200>
# ./spec/controllers/posts_controller_spec.rb:59:in `block (4 levels) in <top (required)>'
This is the code from the spec being tested. I'm sure it's not the most efficient way of doing this, but it does work.
def create
#post = Post.new(
:text => post_params[:text],
:embed => post_params[:embed],
:user => current_user,
:title => post_params[:title],
:tag_list => post_params[:tag_list],
:cagegory_ids => post_params[:category_ids]
)
if #post.save
redirect_to #post
else
render 'new'
end
end
...
private
def post_params
params.require(:post).permit(:title, :text, :embed, :user_id, :tag_list,
:category_ids => [])
end
Here's the factory.
FactoryGirl.define do
factory :post do
title { Faker::Lorem.characters(char_count = 20) }
text { Faker::Lorem.characters(char_count = 150) }
user
categories {
Array(5..10).sample.times.map do
FactoryGirl.create(:category)
end
}
end
end
And the relevant part of the spec
describe "POST #create" do
context "with valid attributes" do
it "saves the new post" do
expect{
post :create, post: FactoryGirl.create(:post).attributes
}.to change(Post,:count).by(1)
end
it "redirects to the post" do
post :create, post: FactoryGirl.create(:post).attributes
response.should redirect_to Post.last
end
end
end
The other test, "saves the new post," works fine. I've tried other variations of the redirect_to line, such as "redirect_to(posts_path(assigns[:post])) and it throws the same error.
Any ideas?
OK, I fixed my problem. It isn't pretty but it works.
The issue is with Factory Girl and associations, and I'm definitely not the first to have that problem, but none of the other solutions worked.
I ended up adding this before :each at the top of the POST #create section...
describe "POST #create" do
before :each do
Post.destroy_all
#cat = FactoryGirl.create(:category)
#newpost = FactoryGirl.build(:post)
#post_params = {category_ids: [#cat.id]}.merge(#newpost.attributes)
end
...
...to put the post params into a new hash that I could call later on in the code like this...
it "saves the new post" do
expect{
post :create, post: #post_params
}.to change(Post,:count).by(1)
end
it "redirects to the post" do
post :create, post: #post_params
response.should redirect_to Post.last
end
So this is solved. It adds a bit of overhead to the test but it works. I won't mark this as THE solution for a couple of days in case someone else comes around with some better, simpler code. I definitely welcome any more ideas.
I assume the problem is in factory. Most likely post instance didn't pass validations and controller renders new view, which is not redirect, but success 200. Add logger in the controller and take a look if record actually saves. You can also read through test log tail -f log/test.log.