Reputation: 8331
I'm fairly new to testing, so I've been struggling with using the correct syntax, especially regarding mocks
.
I want to test my destroy
action in cars_controller.rb
def destroy
if current_user.cars.exists?(params[:id])
car = current_user.cars.find(params[:id])
# only destroy the car if it has no bookings
car.destroy unless car.bookings.exists?
end
redirect_to user_cars_path(current_user)
end
It was rather easy testing the case, when no bookings are associated with the car.
describe CarsController, type: :controller do
let(:user) { create(:user_with_car) }
before { login_user(user) }
describe "DELETE #destroy" do
let(:car) { user.cars.first }
context "when the car has no bookings associated to it" do
it "destroys the requested car" do
expect {
delete :destroy, user_id: user.id, id: car.id
}.to change(user.cars, :count).by(-1)
end
end
But this test is driving me nuts:
context "when the car has bookings associated to it" do
it "does not destroy the requested car" do
##### This line fails miserably
allow(car).to receive_message_chain(:bookings) { [ Booking.new ]}
expect {
delete :destroy, user_id: user.id, id: car.id
}.to change(user.cars, :count).by(0)
end
end
end
end
I do not want to create bookings in the database and associate them with the car. As I understand, it is recommended to mock these bookings, since they have no use further on.
Next to:
allow(car).to receive_message_chain(:bookings) { [ Booking.new ]}
I've had multiple attempts with other syntax', but all failed. I even tried using rpsec-mocks old syntax: stub(...)
.
How would I accomplish this?
Upvotes: 0
Views: 1139
Reputation: 27747
The reason this isn't working, is that the delete action loads its own version of car
- it isn't using the local variable you have declared locally to your spec. So any stubs you add to your local variable will not actually exist on the brand new copy of car
that is inside the controller action.
There are a couple of ways around this.
any_instance_of(Car)
The difference between these options is a tradeoff between being tightly entangled with the internals of your code (ie harder to maintain), speed of running, or actually testing out all the aspects of your code.
The third one makes sure that everything really works (you have a real car with a real booking), but it's slower because it sets up actual models in the db... and that's what you're stubbing to get past.
The first and second are up to you. I personally have an "ick" feeling about stubbing out find when you're testing a controller where finding the car is part of what hopefully you're testing...
plus - it's only ever going to find the car you set up before, so you might as well do a stub on any instance.
SO:
expect_any_instance_of(Car).to receive(:bookings).and_return([ Booking.new ])`
will probably do the trick.
Rspec any_instance_of doco
Upvotes: 1