I recently upgraded my Rails 4 application from RSpec 2.X to 2.99 and despite having run Transpec already, some of my tests are still failing.
require 'spec_helper'
describe Invoice, :type => :model do
before :each do
#user = FactoryGirl.create(:user)
#invoice = FactoryGirl.create(:invoice, :user => #user)
end
it "is not open" do
expect {
FactoryGirl.create(:payment, :invoice => #invoice, :amount => 100)
}.to change{#invoice.reload.open?}.from(true).to(false)
end
it "is open" do
expect {
FactoryGirl.create(:payment, :invoice => #invoice, :amount => 99.99)
}.to_not change{#invoice.reload.open?}.to(false)
end
end
The first test passes just like before the RSpec upgrade.
The second test, however, throws an error:
Failure/Error: expect {
`expect { }.not_to change { }.to()` is deprecated.
What must I change my syntax to?
I've already tried a couple of things like not_to, be_falsey etc. Nothing worked so far.
Thanks for any help.
Don't assert that the value doesn't change to something, just assert that it doesn't change:
it "is open" do
expect {
FactoryGirl.create(:payment, :invoice => #invoice, :amount => 99.99)
}.to_not change { #invoice.reload.open? }
end
That doesn't test the initial value of #invoice.reload.open?, but you should have a separate test for that anyway. You don't need to test it again in this test.
Nonetheless, in RSpec 3 you can use .from by itself to test that the value that doesn't change has a given initial value:
it "is open" do
expect {
FactoryGirl.create(:payment, :invoice => #invoice, :amount => 99.99)
}.to_not change { #invoice.reload.open? }.from(false)
end
You can't do that yet in RSpec 2; .to_not change {}.from passes if the value passed to .from is not what is expected. In RSpec 2.99 that causes a warning.
Related
I have this in controllers
def destroy
#post = Post.find(params[:id])
#post.destroy
end
But I'm lost as to how to actually test if it works. Any pointers would be highly appreciated! I currently have this in my RSpec file:
require 'rails_helper'
RSpec.describe Post, type: :model do
it "must have a title" do
post= Post.create
expect(post.errors[:title]).to_not be_empty
end
it "must have a description" do
post= Post.create
expect(post.errors[:description]).to_not be_empty
end
it "must have a location" do
post= Post.create
expect(post.errors[:location]).to_not be_empty
end
it "must have an image" do
post= Post.create
expect(post.errors[:image]).to_not be_empty
end
it "can be destroyed" do
post= Post.destroy
end
end
You can check if the count of thing has change by -1, like this:
expect { delete '/things', :thing => { :id => 123'} }.to change(Thing, :count).by(-1)
This means that you want to have one less 'thing' and and is ensuring that something has been deleted.
If you want to ensure that specific "thing" was deleted, you can create one before the test, pass the "thing" id as param, and ensure that this doesn't exists on database, like this:
thing = create(:thing)
delete '/things', :thing => { :id => thing.id'}
expect(Thing.find_by(id: thing.id)).to be_nil
As pointed out, if you use request specs ( see https://relishapp.com/rspec/rspec-rails/v/3-9/docs/request-specs/request-spec ) you can easily call the API that should delete the model, and then do an ActiveRecord query to expect no results.
require "rails_helper"
RSpec.describe "delete thing api" do
it "deletes thing" do
// Create a thing with a factory of your choice here
delete "/things", :thing => {:id => 1}
expect(Thing.all.count).to be 0
end
end
I currently have the following situation:
ChallengeRequestsController #new - creates a ChallengeRequest along with another model needing a recpient_id.
def create
#challenge_request = ChallengeRequest.new(challenge_params)
recipient = User.find(params.require(:recipient_id))
# Rest of method redacted.
end
def challenge_params
params.require(:challenge_request).permit(:action)
end
With this controller, I have an RSpec test as follows:
RSpec.describe ChallengeRequestsController, type: :controller do
describe "POST #create" do
context "with valid challenge request" do
let(:valid_challenge_request) { build(:challenge_request) }
context "with non-exisistent recpient id" do
it "throws an error" do
expect{
post :create, :params => {
:challenge_request => {
:challenge_request => valid_challenge_request
},
:recipient_id => 10000
}
}.to raise_error ActiveRecord::RecordNotFound
end
end
end
end
end
Just for reference, here's the Factory:
FactoryGirl.define do
factory :challenge_request do
action { Faker::LeagueOfLegends.champion }
end
end
All of this code works and the test passes, my question is how can I refactor this in a way that I don't need to use the ugly nesting in the request?
:challenge_request => {
:challenge_request => valid_challenge_request
}
When posting the request without this nesting:
post :create, :params => {
:challenge_request => valid_challenge_request,
:recipient_id => 10000
}
The challenge_request is empty when the ChallengeRequestController receives the request.
It would probably work if you change your valid_challenge_request to be a hash of attributes instead of the model itself.
let(:valid_challenge_request) { FactoryBot.attributes_for(:challenge_request) }
You're trying to send along an instance of a model as a parameter to a controller, and something is doing something (I can't find what's doing what to the model) to try and coerce it into something that a browser might send the controller. That conversion is turning the model into an empty string, which is not present or the value false and thus causes the require to throw the ParameterMissing
I am trying to resolve an issue with my rspec test to create an object but the count doesn't seem to change whatever i try. I am sure i am missing something very basic here.
Here is my rspec:
before do
login_account_admin(user)
#group = Factory(:group, :code => "GR_111", :description => "description for GR_111")
Group.stub!(:find).and_return(#group)
end
describe "#create" do
it "should create a new group object" do
group_params = {:code => "NEW_GROUP", :description => "description for NEW_GROUP"}
expect {
post :create, :service_id => service, :cdb_group => group_params, :button => "save", :format => "js"
}.to change(Group, :count).by(1)
end
it "should not create a new group object with invalid code format" do
group_params = {:code => "invalid", :description => "description for invalid code name group"}
expect {
post :create, :service_id => service, :cdb_group => group_params, :button => "save", :format => "js"
}.to_not change(Group, :count)
end
end
"code" parameter can only contain uppercase letters A to Z, 0-9 and _
Here is the controller method definition for #create
def create
#group = Group.new(params[:cdb_group])
respond_to do |format|
if params[:button] == "cancel"
format.js { render "hide_new"}
elsif #group.save
format.js {
render 'show_new_group'
}
format.html { redirect_to(some_path(#service), :notice => 'Group was successfully created.') }
format.xml { head :ok }
end
end
end
Here is the Group model:
class Group < ActiveRecord::Base
validates_uniqueness_of :code
validates_presence_of :code, :description
validates_format_of :code, :without => /[^A-Z0-9_]/ , :message => 'can only contain uppercase letters A to Z, 0-9 and _'
end
Whenever i try to run the rspec test I get the following errors:-
1) GroupsController User As Account Admin goes to #create should create a new group object
Failure/Error: expect {
count should have been changed by 1, but was changed by 0
# ./spec/controllers/groups_controller_spec.rb:51
2) GroupsController User As Account Admin goes to #create should not create a new group object with invalid code format
Failure/Error: expect {
count should not have changed, but did change from 2 to 1
# ./spec/controllers/groups_controller_spec.rb:58
Any help in this regard would be highly appreciated?
Whenever our tests give us unexpected trouble, it's important to take a step back and re-evaluate our approach. Usually, this is an indication of some design problem, either with the code we're testing or with tests themselves.
While it sounds like using a truncation strategy has fixed this particular problem (see more on that below), i would suggest that there is more to learn from the situation.
Consider the two examples from your spec above. The only difference between them comes down to whether the code parameter is valid or not. I would argue that these examples are really testing the Group model, not the controller.
Now, if we're confident in our model test coverage, then we can take a different approach to the controller spec. From the controller's perspective, the model is a collaborator and in general, we always want to avoid indirectly testing collaborators. In this case, we can use a mock to simulate the behavior of the Group model and only test the controller behavior in isolation.
Something like this (please note the code below is incomplete and untested):
# spec/controllers/groups_controller_spec.rb
describe "#create" do
before do
# use a Test Double instead of a real model
#new_group = double(Group)
#params = { :cdb_group => 'stub_cdb_group_param', :service_id => service }
# using should_receive ensures the controller calls new correctly
Group.should_receive(:new).with(#params[:cdb_group]).and_return(#new_group)
end
context "when cancelled responding to js" do
it "renders hide_new" do
post :create, #params.merge({:button => "cancel", :format => "js"})
expect(response).to render_template('hide_new')
end
end
context "with valid params" do
before do
#new_group.should_receive(:save).and_return(true)
end
context "responding to json" # ...
context "responding to html" # ...
context "responding to xml" #...
end
context "with invalid params" do
before do
#new_group.should_receive(:save).and_return(false)
end
# ...
end
end
While the above doesn't specifically address the problem with record counts you were having, i suspect the problem may go away once you isolate your test targets correctly.
If you choose to stick with database truncation, consider using it selectively as described here.
I hope at least some of that helps :).
After fiddling with my spec_helper.rb file. It turns out that i have to change my database cleaning strategy to truncation. Here is my spec_helper file, for reference (https://gist.github.com/aliibrahim/7152042)
I changed this line in my code and disable use of transactional_fixtures
config.use_transactional_fixtures = false
and my database cleaning strategy is now:
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
This gives a clear database before the start/end of every scenario. Hope this helps anyone!
You should test...
1) Group.create(group_params).should be_true after group_params = ...
If this fails, the problem probably related to model or test environment.
2) response.status.should == 302 after post ...
If this fails, the problem probably related to session (authentication / authorization).
3) assigns(:group).should be_valid after post ...
If this fails, the problem probably related to controller.
After a factory girl create I have the following
it "should display the email of user if public" do
#user.update_attributes(:public_email => true)
# I have also tried
# #user.public_email = true
# and
# #user.toggle!(public_email)
#user.save
puts "EMAIL IS #{#user.public_email}"
get :show, :id => #user.id
response.should have_selector("dt", :content => "Email")
end
Rspec will print that "EMAIL IS true" but in the view, public_email is not true (I have <%= "User email is #{#user.public_email}." %> and that prints false).
All my other tests work as expected and it works fine in development.
Why is this happening?
EDIT:
This is my own fault. I was trying to avoid writing the line get :show, :id => #user.id a bunch of times, so I had another before(:each) do call after those tests, and I thought by listing it after them it would not count, but apparently it does.
I'm sorry, but this is beginning to feel like kicking myself in the head. I'm completely baffled by RSpec. Have watched video after video, read tutorial after tutorial, and still I'm just stuck on square one.
=== here is what I'm working with
http://github.com/fudgestudios/bort/tree/master
=== Errors
F
1)
NoMethodError in 'bidding on an item should work'
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.new_record?
spec/controllers/auction_controller_spec.rb:16:
spec/controllers/auction_controller_spec.rb:6:
Finished in 0.067139 seconds
1 example, 1 failure
=== here is my controller action
def bid
#bid = Bid.new(params[:bid])
#bid.save
end
=== here is my test
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "bidding on an item" do
controller_name :items
before(:each) do
#user = mock_user
stub!(:current_user).and_return(#user)
end
it "should work" do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
assigns[:bid].should be_new_record
end
end
=== spec_helper
http://github.com/fudgestudios/bort/tree/master/spec/spec_helper.rb
It's very disheartening to wake for work at 3 a.m. and accomplish nothing for the day. Please understand.
You've got a couple of things backwards in before(:each). Seeing as the example is specifying that the post should increase the count by 1, you're dealing with real records and there is no reason for stubbing anything at all. Also, at this point, since there is only one example, there is no reason to have a before block. I'd do it this way:
describe ItemsController, "bidding on an item" do
fixtures :users
it "should create a new Bid" do
login_as :quentin
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
end
One thing I'd recommend is creating these things VERY granularly for now until you understand them better. Start with the expectation (post should change bid count), run the spec and let the failure message guide you to add whatever else you need in the spec or in the code.
Jesse,
It'll still pass if you comment out the 2nd two lines of before(:each), which are having no impact on the "should create a new Bid" example.
The lambda keyword creates an arbitrary block of code that is not executed when you define it, but is actually an object you can assign to a variable and execute later:
the_post = lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end
At this point that code is not executed, but we can refer to it with the 'the_post' variable. Now we can send it 'should', followed by 'change ...', like this:
the_post.should change(Bid, :count).by(1)
When this line is executed, a few things happen. The material to the right of 'should' is evaluated first, initializing an rspec matcher object with some instructions. That matcher is the argument to 'should' - the equivalent of this:
matcher = change(Bid, :count).by(1)
the_post.should(matcher)
The 'should' method is called on the_post, which is the code block (that still hasn't been executed). Under the hood, the 'should' method passes self (the_post) to the matcher, so the matcher now has everything it needs to evaluate the example.
The matcher calls Bid.count and records the value. Then it executes the block (the_post), and then calls Bid.count a second time and compares it to the value it recorded earlier. In this case, since we're looking for Bid.count to change by 1 (positive is implicit here - increase by 1), if that's what happens the matcher stays silent and the example passes.
If the values are the same, or differ by some value other than 1, the example will fail. You can see that work if you change the expectation to by(2) instead of by(1).
HTH,
David
EDIT: you shouldn't expect Bid.count to increment when using a mock object. Mantra I forgot: caffeine before code.
Just commenting out the lines, for now, so the original is still there.
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "POST to bid_controller" do
controller_name :items
before(:each) do
##bid = mock_model(Bid) # create a new mock model so we can verify the appropriate things
#Bid.stub!(:new).and_return(#bid) # stub the new class method on Bid to return our mock rather than a new ActiveRecord object.
# this separates our controller spec entirely from the database.
end
it "should create a new Bid" do
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
# ... more specs
end
Try to write as small specs as possible, write your setences in such a way as to make it obvious what you should be verifying in that spec. For example, how I changed yours from it "should work" to it "should create a new Bid". If there's more to that controller, write a new spec
for each small piece of functionality.
If you do end up needing mock users, there are some helpers for restful_authentication that make it easier. First create a user fixture in
RAILS_ROOT/spec/fixtures/users.yml, like this:
quentin:
login: quentin
email: quentin#example.com
salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
created_at: <%= 5.days.ago.to_s :db %>
activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9b
activated_at: <%= 5.days.ago.to_s :db %>
name: "Quentin"
Then in your spec you will be able to write the following and have your current_user method and all the other parts of restul_authentication
behave as you would expect them to at runtime.
login_as :quentin
# .... the rest of your spec
As an example of a few more specs I might add as a couple more examples:
def do_post
# extracting the method under test, so I don't repeat myself
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end
it "should create a new Bid" do
lambda do
do_post
end.should change(Bid, :count).by(1)
end
it "should assign the Bid to the proper auction" do
#bid.should_receive(:auction_id=).with(1) # test this, I believe doing Bid.new(params[:bid]) sets the id directly not sets the model
do_post
end
it "should assign the Bid the proper points" do
#bid.should_receive(:point=).with(1)
do_post
end
While I don't quite understand what's going on. (with stubs and the lambda)....
for
def bid
#bid = Bid.new params[:bid]
#bid.save
end
The following passes !!
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "bidding on an item" do
controller_name :items
fixtures :users
before(:each) do
#user = login_as :quentin
#bid = mock_model(Bid) # create a new mock model so we can verify the appropriate things
#bid.stub!(:new).and_return(#bid) # stub the new class method on Bid to return our mock rather than a new ActiveRecord object.
#Bid.stub!(:save).and_return(true)# this separates our controller spec entirely from the database.
end
it "should create a new Bid" do
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
end