Reputation: 730
I'm trying to test a ruby controller method, and I'm expecting multiple things to change in the database.
context "With an unknown user" do
let(:unknown_phone_number) { "0000000000" }
subject {post :create, twiml_message(unknown_phone_number, "YES", "To" => twilio_phone_number) }
it { response.should change(Card, :count).by(1) }
it { should change(User, :count).by(1) }
it { should change(Customer, :count).by(1) }```
end
Gives the error
NoMethodError: undefined method `call' for #<ActionController::TestResponse:0x007fbd5d643030> ./spec/controllers/api/v1/sms_controller_spec.rb:25:in `block (4 levels) in <top (required)>'```
What am I missing, or am I barking up the completely wrong tree?
Version info: Rails 1.9.3 Rspec 2.11.0
Update: Based on Answer
it "registers an unknown user" do
unknown_phone_number = "0000000000"
expect {
post :create, twiml_message(unknown_phone_number, "YES", "To" => twilio_phone_number)
}.to change(Card, :count).by(1)
open_last_text_message_for unknown_phone_number
current_text_message.should have_body "Welcome to OnTab. You are now registered with a test account. To get started text TAB to open a tab."
end
it "registers an unknown user" do
unknown_phone_number = "0000000000"
expect {
post :create, twiml_message(unknown_phone_number, "YES", "To" => twilio_phone_number)
}.to change(Customer, :count).by(1)
open_last_text_message_for unknown_phone_number
current_text_message.should have_body "Welcome to OnTab. You are now registered with a test account. To get started text TAB to open a tab."
end
it "registers an unknown user" do
unknown_phone_number = "0000000000"
expect {
post :create, twiml_message(unknown_phone_number, "YES", "To" => twilio_phone_number)
}.to change(User, :count).by(1)
open_last_text_message_for unknown_phone_number
current_text_message.should have_body "Welcome to OnTab. You are now registered with a test account. To get started text TAB to open a tab."
end
But this runs the post action three times. It there any way to do this, but only execute the POST once?
Upvotes: 5
Views: 5745
Reputation: 6961
You probably want this type of test as an integration or request spec. Controllers should do more about asserting messages passed to models and setup of the view / return state. Though, to answer the question, the response
object doesn't have a call
method on it. It is the returned state - things have already changed. You want to assert that the post
does the changing.
The expect change
matcher needs to be attached to the block form of expect:
it do
expect{ post :create, ... }.to change(User, :count).by(1)
end
Your current subject is not an expectation block, but the return value of the post
action. So the implicit subject will not work for change
. Also, as I stated above, you cannot use response.should
for that same reason. You need to use the block form.
For this type of test, you will not be able to use the implicit subject, due to how RSpec handles expect{}.
versus should
.
Also, arguably, to make the subject an expectation block would change the responsibilities and roles. In general the object under test is that, an object. By setting it to an action you're changing this and will likely confuse others down the road. This may seem odd in this case, but that is due to Rails and how you interact with controller objects.
Yes, it will make three post
requests. I'm going to assume you only want to make one request because of "performance". That can be an issue but there are many things you can do to help mitigate performance instead of simply making one request.
One philosophy for speeding up tests is to stub Active Record in controller tests, that keeps things short and fast. Integration specs are then used for the type of testing you are talking about (read http://www.andylindeman.com/2012/11/11/rspec-rails-and-capybara-2.0-what-you-need-to-know.html). Yes, integration tests are slower normally; that is the price of doing a full stack test.
RSpec has a philosophy of 'test one thing'. You want to re-execute the object / method under test each time. This ensures that there is no cross test contamination which can lead to faulty results. As it is written, I would say you are already violating this be testing a database update and testing a text message expectation.
context 'registering a new user' do
it 'saves the user in the database' do
unknown_phone_number = '000'
new_card = mock_model(Card)
expect(Card).to receive(:new).and_return(new_card)
expect(new_card).to receive(:save).and_return(true)
post :create #...
end
it 'sends a welcome message to the phone' do
# You can stub the text class here and assert on messages passed
# As you probably trust that if the message is passed the text message code
# does what it was designed to do (you have unit tests for it right?)
end
end
Upvotes: 2