views:

1027

answers:

4

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.

+1  A: 

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: [email protected]
  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
Otto
A: 

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
Jesse
+10  A: 

Hey Jesse,

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.

HTH, David

I think David managed to put it more succinctly than I did.
Otto
+1  A: 

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